后序遍历递归实现最直观,顺序为左→右→根,需先判空再递归左右子树并最后加入根值;迭代推荐双栈法,第一栈按左→右压入子节点以使第二栈逆序存储,最终弹出即得正确结果。

递归实现 postorderTraversal 最直观,但要注意空节点处理
后序遍历顺序是「左子树 → 右子树 → 根节点」,递归写法天然贴合这个逻辑。关键在于每次进入函数时先判空,否则访问 nullptr 的 left 或 right 会崩溃。
实操建议:
- 函数参数通常为
TreeNode*,入口调用传入根节点即可 - 结果存入引用传入的
vector<int>&</int>,避免频繁拷贝 - 递归三步必须严格按「左→右→根」顺序,调换会导致结果错误
void postorderTraversal(TreeNode* root, vector<int>& res) {
if (!root) return;
postorderTraversal(root->left, res);
postorderTraversal(root->right, res);
res.push_back(root->val);
}</int>
迭代实现要用两个栈,单栈容易搞混访问和打印时机
迭代版后序遍历比前序/中序难,因为根节点要最后访问,但栈是后进先出——直接模拟会导致根被提前弹出。常见错误是只用一个栈、靠标记位或反转输出来“凑”,可读性差且易错。
推荐双栈法:第一个栈控制遍历顺序(类似前序),第二个栈暂存待输出节点。
- 第一栈压入根,每次弹出后按「左→右」顺序压入子节点(这样第二栈里就是「根→右→左」)
- 所有节点转入第二栈后,依次弹出即得「左→右→根」
- 注意:不能把
root->right放在root->left前压入第一栈,否则第二栈顺序就反了
vector<int> postorderTraversal(TreeNode* root) {
if (!root) return {};
stack<treenode> s1, s2;
s1.push(root);
while (!s1.empty()) {
TreeNode* node = s1.top(); s1.pop();
s2.push(node);
if (node->left) s1.push(node->left);
if (node->right) s1.push(node->right);
}
vector<int> res;
while (!s2.empty()) {
res.push_back(s2.top()->val);
s2.pop();
}
return res;
}</int></treenode></int>
用 nullptr 标记已访问节点的单栈法,空间更省但逻辑绕
如果不想用双栈,可用单栈 + nullptr 作为访问标记。思路是:第一次遇到某节点时,先压入其右左子(保证左先出),再压入自身加一个 nullptr;当弹出 nullptr 时,说明它下面那个节点左右子都已处理完,可以输出。
- 每次看到
nullptr就弹出栈顶并加入结果,不继续压子节点 - 非空节点才按「右→左」顺序压子节点,再压自己和
nullptr - 这个写法调试困难,一不小心就把
nullptr和节点顺序弄反,导致无限循环或漏节点
LeetCode 提交时注意 TreeNode 定义和返回值类型
不同平台对树节点定义略有差异,比如 LeetCode 使用的是带默认构造函数的结构体,而本地编译可能需手动补全。常见报错如 invalid use of incomplete type 'struct TreeNode' 就是因为没包含定义。
- 务必确认题目给的
TreeNode结构,尤其是val、left、right成员名是否一致 - 函数签名必须严格匹配:返回
vector<int></int>,参数为TreeNode*,别写成指针引用或智能指针 - 空输入
nullptr必须返回空vector,返回{0}或未初始化容器都会判错
递归最稳,双栈最易懂,单栈标记法看似省空间,但多一层间接跳转,CPU cache 友好性反而下降,真要优化不如先确认是不是瓶颈所在。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











