GESP 7级

二叉树遍历

7级 · DP/图论/搜索

📖 一句话理解

二叉树是一种每个节点最多有两个子节点的树形结构。遍历就是"按照某种顺序访问所有节点"。常见的遍历方式有四种:前序、中序、后序(都是DFS/递归)和层序(BFS)。
💡 为什么重要?
二叉树遍历是递归思维的最佳训练。掌握它后,理解图的DFS/BFS、回溯算法会容易很多。而且二叉搜索树(BST)的中序遍历就是有序序列,这在很多题目中有妙用。GESP 7级考试中,前序/中序/后序遍历的代码和输出是必考内容。
📋 前置知识
  • 递归:函数调用自己,理解递归终止条件
  • 指针/引用:TreeNode* 指针的使用
  • struct结构体:定义节点类型

🧠 什么是二叉树?

📝 一棵二叉树的样子

       1             (根节点)
   /   \           (左子节点、右子节点)
  2     3
/  \        
4   5

节点1是根,有左子2和右子3。节点2有左子4和右子5。

📐 四种遍历方式

📐 口诀(记住"根"的位置)
前序:根 → 左 → 右   (根在最前面)
中序:左 → 根 → 右   (根在中间)
后序:左 → 右 → 根   (根在最后面)
层序:逐层从左到右   (BFS方式)

用例子理解

上面的二叉树:

📊 四种遍历的结果

前序(根左右):1, 2, 4, 5, 3
中序(左根右):4, 2, 5, 1, 3
后序(左右根):4, 5, 2, 3, 1
层序(逐层):1, 2, 3, 4, 5

💻 代码实现

💻 节点定义
// 二叉树节点定义
struct TreeNode {
    int val;            // 节点的值
    TreeNode *left;     // 左子节点指针
    TreeNode *right;    // 右子节点指针

    // 构造函数:方便创建节点
    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
💻 前序遍历(逐行注释)
void preorder(TreeNode* root) {
    if (!root) return;              // 终止条件:空节点,直接返回

    cout << root->val << " ";     // ① 访问根节点(先处理根)
    preorder(root->left);          // ② 递归遍历左子树
    preorder(root->right);         // ③ 递归遍历右子树
}

// 执行过程(以根=1为例):
// preorder(1) → 打印1
//   preorder(2) → 打印2
//     preorder(4) → 打印4
//     preorder(5) → 打印5
//   preorder(3) → 打印3
// 输出:1 2 4 5 3
💻 中序遍历(逐行注释)
void inorder(TreeNode* root) {
    if (!root) return;              // 终止条件

    inorder(root->left);           // ① 先遍历左子树
    cout << root->val << " ";     // ② 再访问根节点
    inorder(root->right);          // ③ 最后遍历右子树
}

// ⭐ 重要性质:BST的中序遍历结果是有序的!
// 如果是二叉搜索树(左子树值 < 根值 < 右子树值),
// 中序遍历出来的序列一定是升序的。
💻 后序遍历(逐行注释)
void postorder(TreeNode* root) {
    if (!root) return;              // 终止条件

    postorder(root->left);         // ① 先遍历左子树
    postorder(root->right);        // ② 再遍历右子树
    cout << root->val << " ";     // ③ 最后访问根节点
}

// 后序遍历的应用:释放二叉树内存
// 必须先释放子节点,再释放根节点(后序天然满足这个顺序)

🔢 根据遍历结果重建二叉树

💡 经典考法
已知前序+中序(或后序+中序),重建二叉树。
方法:前序的第一个元素就是根,在中序中找到根的位置,左边是左子树,右边是右子树。
💻 由前序+中序建树
#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;

struct TreeNode {
    int val;
    TreeNode *left, *right;
    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};

unordered_map<int,int> inMap;  // 中序值→下标的映射,方便快速查找

TreeNode* build(vector<int>& pre, int preL, int preR,
                vector<int>& in, int inL, int inR) {
    if (preL > preR) return nullptr;   // 空区间

    int rootVal = pre[preL];           // 前序第一个就是根
    TreeNode* root = new TreeNode(rootVal);

    int inRoot = inMap[rootVal];       // 在中序中找到根的位置
    int leftSize = inRoot - inL;       // 左子树的大小

    // 递归构建左子树和右子树
    root->left = build(pre, preL+1, preL+leftSize, in, inL, inRoot-1);
    root->right = build(pre, preL+leftSize+1, preR, in, inRoot+1, inR);

    return root;
}

// 调用:
// for (int i = 0; i < n; i++) inMap[in[i]] = i;
// TreeNode* root = build(pre, 0, n-1, in, 0, n-1);

📊 遍历方式对比

📝 什么时候用哪种?
  • 前序:构建/复制二叉树(先创建根,再创建子树)
  • 中序:BST中得到有序序列
  • 后序:释放内存(先释放子节点再释放父节点)
  • 层序:按层处理,求树的宽度

🚨 易错点

⚠️ 常见错误汇总
  1. 忘记终止条件:if (!root) return; 是必须的,否则空节点会导致空指针异常。
  2. 前序/中序/后序搞混:记住口诀"前根、中根、后根"——看"根"在前、中、后哪个位置。
  3. 遍历结果记反:前序是"根左右"不是"左右根",后序是"左右根"不是"根左右"。多画几棵树手动推演。
  4. 中序遍历≠按大小排序:只有BST的中序遍历才是有序的,普通二叉树不是。
  5. 重建二叉树时左右子树大小算错:前序中根后面的 leftSize 个元素是左子树,不是中序中根前面的个数。
  6. 层序遍历忘记记录size:如果需要按层处理,必须在循环开始时记录当前层的节点数。

🎯 练习建议

📝 循序渐进练习路径
  1. 画图理解:画一棵二叉树,手动写出四种遍历的结果。
  2. 写递归代码:三种递归遍历(前序、中序、后序)要能默写。
  3. 做经典题目:
    • P1305 新二叉树(洛谷入门)
    • P1229 遍历问题(已知前序+后序求中序有多少种)
    • P1827 美国血统(已知前序+中序输出后序)
    • LeetCode 144 前序遍历 / 94 中序遍历 / 145 后序遍历
  4. 进阶:学习非递归(迭代)实现遍历,以及Morris遍历。