后序遍历的口诀是**「左右根」**:先递归遍历左子树,再递归遍历右子树,两边都回来之后最后记录当前根。它是三种 DFS 里"根"出场最晚的一种——节点要等自己的整棵左子树和整棵右子树都被处理完,才轮到它被记录。
本动画用同一棵 7 节点的完全二叉搜索树演示(默认 2s/拍,全片 22 拍):
树:根 5(左子 3,右子 8);3 的孩子 1、4;8 的孩子 7、9
后序输出:1 4 3 7 9 8 5
节拍:宣告 1 + 进入 7 + 访问 7 + 返回 6 + 完成 1
看点在**"根最后落字":输出条的前 6 格全是两棵子树的节点,根 5 压轴在最后一格才出现——而且输出序列的最后一个元素永远是整棵树的根**。叶子先记、越往上越晚,整条输出是一张"自底向上"的顺序表。
进入 v · 暂不记录——先序在这一拍就记了,后序必须等;返回 p · 查完 n,带着"孩子都处理完了"的语义向上移动;✓ 完成 | 后序遍历 7 节点全部输出。| 项目 | 复杂度 | 说明 |
|---|---|---|
| 时间 | O(n) | 每个节点进入一次、访问一次、返回一次 |
| 空间 | O(h) | 递归栈深 = 树高 h |
| 迭代版 | O(h) | 前序(根右左)逆序 = 后序;或记录"上次访问节点"判断右子树是否已回来 |
| 输出性质 | — | 序列末元素必为根;任意位置的根都在其整个子树之后 |
#include <iostream>
struct Node {
int val;
Node *left, *right;
Node(int v) : val(v), left(nullptr), right(nullptr) {}
};
void postorder(Node *root) {
if (!root) return;
postorder(root->left); // 左:左子树全部处理完
postorder(root->right); // 右:右子树也处理完
std::cout << root->val << " "; // 根:最后才记录
}
int subtreeSum(Node *root) { // 后序的典型用途:先算完两棵子树
if (!root) return 0;
return subtreeSum(root->left) + subtreeSum(root->right) + root->val;
}
int main() {
// 完全 BST:5(3(1,4), 8(7,9))
Node a(1), b(4), c(3), d(7), e(9), f(8), root(5);
c.left = &a; c.right = &b;
f.left = &d; f.right = &e;
root.left = &c; root.right = &f;
postorder(&root); // 输出 1 4 3 7 9 8 5
return 0;
}
凡是**"孩子没算完就不能算父亲"的场合,都是后序的主场:删除整棵树(必须先删孩子再删父亲,否则悬空指针)、目录/文件大小汇总(文件夹大小 = 子项之和,自底向上累加)、表达式树求值(先算两个操作数才能做运算,对应后缀表达式 / 逆波兰式)、树形 DP(父节点的状态由子节点状态推出)、以及根据两个遍历序列重建树**(后序末位定根)。判断依据一句话:需要拿子问题的结果往上交,就用后序——动画里根 5 永远最后一个落字,就是这个道理。