🌀

二叉树的遍历:前/中/后序

"遍历"就是把树拉直成一条序列——差别只在于:自己排在"前、中、后"哪个位置

💡 打个比方

想象你是一个"打卡员",要走进一棵家谱树,给每个人拍一张照片,一个都不能漏、一个都不能重复。

走到每个人面前时,你有三种"拍照时机"可选:先拍自己再去孩子家(前序)、拍完左孩子再拍自己(中序)、拍完所有孩子最后拍自己(后序)。

规则一旦定了,就要对每个节点一视同仁——这就是"递归":对左子树用同一套规则,对右子树也用同一套规则。

遍历:按照某种规则,访问二叉树中的每一个节点恰好一次。二叉树有三种经典顺序:前序(根→左→右)、中序(左→根→右)、后序(左→右→根)。

🖼️ 大图看明白

我们的示例树 1 2 3 4 5 前序:先拍自己,再左后右 1 2 4 5 3 中序:先左,再拍自己,后右 4 2 5 1 3 后序:左、右都拍完,最后拍自己 4 5 2 3 1 同一棵树,三种"拍照时机",得到三条不同的序列

🧩 子任务 1:为什么要遍历?

🎯 小目标:说清楚"树"和"序列"之间的转换需求

树是二维的(有层次、有分支),但打印、发送、存进文件都是一维的序列。想把一棵树"搬"到一维世界里,就得先定好访问顺序。

三个真实场景:① 打印家谱名单(按某种顺序念出来);② 把树通过网络发给别人(序列 + 规则 = 对方能还原整棵树);③ 统计、计算都要"把每个节点看一遍"。

重点:序列本身不够——必须同时告诉别人"我按什么顺序访问的"。前序序列 + "这是前序"才能还原树(子任务 4 讲原理)。
✅ 小检查:只拿到一串数字 [1,2,4,5,3],能还原出唯一的树吗?
看看答案
不能。必须知道它是"前序"还是别的顺序。顺序是"压缩"时丢掉的另一半信息。

🧩 子任务 2:手推前序(一步一步走)

🎯 小目标:不看代码,只用规则,手算出前序序列

规则:前序 = 先访问自己,再左子树,再右子树。对"左子树"和"右子树"内部,也用同一套规则(递归!)。跟着打卡员走一遍:

① 站在根 1:先拍自己 → 输出 1。

② 去左孩子 2:先拍自己 → 输出 2。

③ 2 的左孩子 4:拍 → 输出 4。4 没有孩子,原路退回 2。

④ 2 的右孩子 5:拍 → 输出 5。退回 2,再退回 1。

⑤ 1 的右孩子 3:拍 → 输出 3。结束!

前序结果:1 2 4 5 3 —— 和大图右边第一行完全一致。规律:前序的第一个数永远是根,这正好是还原树的钥匙。
✅ 小检查:中序规则下,第一个被拍的是谁?最后一个呢?
看看答案
中序 = 左→根→右:最左边的叶子 4 第一个被拍;最右边的叶子 3 最后被拍(根 1 倒数第二)。

🧩 子任务 3:递归代码三兄弟

🎯 小目标:会写前序/中序/后序三个函数,只改"cout 那一行的位置"

三种遍历的代码长得几乎一样,差别只有一处:打印(访问)那行代码放在递归调用的前面、中间、还是后面。先"查空",再"左、自己、右"——顺序一换,世界大变。

// 前序:先访问自己
void pre(Node* t) {
    if (t == nullptr) return;   // 空树:啥也不做(递归出口)
    cout << t->val << " ";        // ① 自己
    pre(t->left);               // ② 左子树(同样规则)
    pre(t->right);              // ③ 右子树(同样规则)
}

// 中序:先左,再自己,后右
void ino(Node* t) {
    if (t == nullptr) return;
    ino(t->left);               // ① 左
    cout << t->val << " ";        // ② 自己
    ino(t->right);              // ③ 右
}

// 后序:左、右都完成后,最后自己
void post(Node* t) {
    if (t == nullptr) return;
    post(t->left);
    post(t->right);
    cout << t->val << " ";        // 最后才是自己
}

对示例树运行:pre 输出 1 2 4 5 3,ino 输出 4 2 5 1 3,post 输出 4 5 2 3 1——和大图完全一致。

为什么必须"查空":t == nullptr 时再访问 t->val 就会"拆一个不存在的盒子"(段错误)。空树 = 递归的终点,就像 GESP5 递归课里"队首的人说前面 0 个人"。
✅ 小检查:把 cout 放在两个递归调用之间,得到的是哪种序?
看看答案
中序(左 → 自己 → 右)。三个函数的骨架完全相同,只有"自己"的位置不同。

🧩 子任务 4:为什么"中序 + 前序"能还原树

🎯 小目标:理解还原的第一步(会推就行,不要求默写代码)

两条序列各给一半信息:前序第一个数 = 根(子任务 2 的规律);中序里,根左边的 = 左子树,右边的 = 右子树(中序 = 左→根→右 决定的)。

还原示例:前序 [1,2,4,5,3],中序 [4,2,5,1,3]。

① 前序首位 = 1 → 根是 1。

② 在中序里找 1:它左边 [4,2,5] 是左子树,右边 [3] 是右子树。

③ 对左子树 [4,2,5] 重复同样的两步:前序对应段 [2,4,5] 首位 = 2 → 左子树的根是 2;中序里 2 左边 [4]、右边 [5] → 2 的左孩子 4、右孩子 5。完成!

一句话:前序负责"告诉我谁是根",中序负责"告诉我谁在根的左边、谁在右边",两者合起来,树就唯一确定了(值互不重复时)。
✅ 小检查:为什么"前序 + 后序"通常不能唯一还原树?
看看答案
后序没有"左边是谁右边是谁"的切分信息。比如只有一个孩子的根,无法判断孩子是左是右——序列对不上形状。

💻 完整 C++ 代码:建树 + 三种遍历

#include <iostream>
using namespace std;

struct Node {
    int val;
    Node* left;
    Node* right;
};

// 小工具:造一个节点
Node* makeNode(int v, Node* l, Node* r) {
    Node* t = new Node();
    t->val = v;
    t->left = l;
    t->right = r;
    return t;
}

void pre(Node* t) {
    if (t == nullptr) return;
    cout << t->val << " ";
    pre(t->left);
    pre(t->right);
}

void ino(Node* t) {
    if (t == nullptr) return;
    ino(t->left);
    cout << t->val << " ";
    ino(t->right);
}

void post(Node* t) {
    if (t == nullptr) return;
    post(t->left);
    post(t->right);
    cout << t->val << " ";
}

int main() {
    // 建示例树:1 为根,2/3 为左右孩子,4/5 是 2 的孩子
    Node* n4 = makeNode(4, nullptr, nullptr);
    Node* n5 = makeNode(5, nullptr, nullptr);
    Node* n2 = makeNode(2, n4, n5);
    Node* n3 = makeNode(3, nullptr, nullptr);
    Node* root = makeNode(1, n2, n3);

    cout << "前序: "; pre(root);  cout << endl;
    cout << "中序: ";  ino(root);  cout << endl;
    cout << "后序: "; post(root); cout << endl;
    // 前序: 1 2 4 5 3
    // 中序: 4 2 5 1 3
    // 后序: 4 5 2 3 1
    return 0;
}

✋ 你来试试

第 1 题:树只有 3 个节点:根 10,左孩子 5,右孩子 20。写出前序、中序、后序。
前序 10 5 20;中序 5 10 20;后序 5 20 10。
第 2 题:示例树有 5 个节点,中序遍历一共访问(打印)多少个节点?
5 个——遍历的定义就是"每个节点恰好访问一次",与顺序无关。
第 3 题:前序序列 [A, B, C]、中序序列 [B, A, C] 的树长什么样?
前序首位 A 是根;中序里 A 左边 [B] 是左子树、右边 [C] 是右子树。所以 A 为根,B 是左孩子,C 是右孩子。
🎯 一句话记住:遍历 = 每个节点恰好拍一次照;前 = 先自己,中 = 夹中间,后 = 最后自己——代码骨架相同,只挪"访问自己"那一行。