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方式)
中序:左 → 根 → 右 (根在中间)
后序:左 → 右 → 根 (根在最后面)
层序:逐层从左到右 (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中得到有序序列
- 后序:释放内存(先释放子节点再释放父节点)
- 层序:按层处理,求树的宽度
🚨 易错点
⚠️ 常见错误汇总
- 忘记终止条件:if (!root) return; 是必须的,否则空节点会导致空指针异常。
- 前序/中序/后序搞混:记住口诀"前根、中根、后根"——看"根"在前、中、后哪个位置。
- 遍历结果记反:前序是"根左右"不是"左右根",后序是"左右根"不是"根左右"。多画几棵树手动推演。
- 中序遍历≠按大小排序:只有BST的中序遍历才是有序的,普通二叉树不是。
- 重建二叉树时左右子树大小算错:前序中根后面的 leftSize 个元素是左子树,不是中序中根前面的个数。
- 层序遍历忘记记录size:如果需要按层处理,必须在循环开始时记录当前层的节点数。
🎯 练习建议
📝 循序渐进练习路径
- 画图理解:画一棵二叉树,手动写出四种遍历的结果。
- 写递归代码:三种递归遍历(前序、中序、后序)要能默写。
- 做经典题目:
- P1305 新二叉树(洛谷入门)
- P1229 遍历问题(已知前序+后序求中序有多少种)
- P1827 美国血统(已知前序+中序输出后序)
- LeetCode 144 前序遍历 / 94 中序遍历 / 145 后序遍历
- 进阶:学习非递归(迭代)实现遍历,以及Morris遍历。