Appearance
二叉树的遍历
二叉树的遍历是指按照某种顺序访问树中的每个节点,使每个节点被访问且仅被访问一次。遍历是二叉树各种操作的基础。
遍历的基本概念
遍历的意义
二叉树的遍历是树结构中最基本、最重要的操作之一。通过遍历,我们可以:
- 获取树中所有节点的信息
- 查找特定节点
- 对树进行各种统计和计算
- 为其他操作(如插入、删除)提供基础
遍历的分类
二叉树的遍历方式主要分为两类:
| 类别 | 遍历方式 | 特点 |
|---|---|---|
| 深度优先遍历 | 前序、中序、后序遍历 | 按深度方向访问节点 |
| 广度优先遍历 | 层序遍历 | 按层次方向访问节点 |
深度优先遍历(DFS)
深度优先遍历(Depth-First Search, DFS)是一种沿着树的深度方向遍历的方式,先访问根节点,再递归访问子树。
前序遍历(Preorder Traversal)
前序遍历的访问顺序:根节点 → 左子树 → 右子树
算法步骤:
- 访问根节点
- 前序遍历左子树
- 前序遍历右子树
递归实现:
cpp
void preorder_traversal(const BinaryTreeNode<int> *root, std::vector<int> &result) {
if (root == nullptr) return;
result.push_back(root->data); // 访问根节点
preorder_traversal(root->left.get(), result); // 遍历左子树
preorder_traversal(root->right.get(), result); // 遍历右子树
}非递归实现(使用栈):
cpp
void preorder_traversal_iterative(const BinaryTreeNode<int> *root, std::vector<int> &result) {
if (root == nullptr) return;
std::stack<const BinaryTreeNode<int>*> stk;
stk.push(root);
while (!stk.empty()) {
auto node = stk.top();
stk.pop();
result.push_back(node->data);
if (node->right) stk.push(node->right.get());
if (node->left) stk.push(node->left.get());
}
}应用场景:
- 复制一棵二叉树
- 计算表达式树的值
- 查找特定节点
中序遍历(Inorder Traversal)
中序遍历的访问顺序:左子树 → 根节点 → 右子树
算法步骤:
- 中序遍历左子树
- 访问根节点
- 中序遍历右子树
递归实现:
cpp
void inorder_traversal(const BinaryTreeNode<int> *root, std::vector<int> &result) {
if (root == nullptr) return;
inorder_traversal(root->left.get(), result); // 遍历左子树
result.push_back(root->data); // 访问根节点
inorder_traversal(root->right.get(), result); // 遍历右子树
}非递归实现(使用栈):
cpp
void inorder_traversal_iterative(const BinaryTreeNode<int> *root, std::vector<int> &result) {
std::stack<const BinaryTreeNode<int>*> stk;
auto current = root;
while (current || !stk.empty()) {
while (current) {
stk.push(current);
current = current->left.get();
}
current = stk.top();
stk.pop();
result.push_back(current->data);
current = current->right.get();
}
}应用场景:
- 二叉搜索树的中序遍历结果是有序序列
- 表达式树的中序遍历可以得到表达式的 infix 形式
后序遍历(Postorder Traversal)
后序遍历的访问顺序:左子树 → 右子树 → 根节点
算法步骤:
- 后序遍历左子树
- 后序遍历右子树
- 访问根节点
递归实现:
cpp
void postorder_traversal(const BinaryTreeNode<int> *root, std::vector<int> &result) {
if (root == nullptr) return;
postorder_traversal(root->left.get(), result); // 遍历左子树
postorder_traversal(root->right.get(), result); // 遍历右子树
result.push_back(root->data); // 访问根节点
}非递归实现(使用栈):
cpp
void postorder_traversal_iterative(const BinaryTreeNode<int> *root, std::vector<int> &result) {
if (root == nullptr) return;
std::stack<const BinaryTreeNode<int>*> stk;
std::stack<const BinaryTreeNode<int>*> output;
stk.push(root);
while (!stk.empty()) {
auto node = stk.top();
stk.pop();
output.push(node);
if (node->left) stk.push(node->left.get());
if (node->right) stk.push(node->right.get());
}
while (!output.empty()) {
result.push_back(output.top()->data);
output.pop();
}
}应用场景:
- 计算树的深度
- 删除二叉树(先删除子节点再删除根节点)
- 计算表达式树的值
广度优先遍历(BFS)
广度优先遍历(Breadth-First Search, BFS),也称层序遍历,按照树的层次从上到下、从左到右依次访问节点。
层序遍历(Level Order Traversal)
算法步骤:
- 将根节点入队
- 循环执行:出队一个节点并访问,将其左右子节点入队
- 直到队列为空
实现代码:
cpp
void level_order_traversal(const BinaryTreeNode<int> *root, std::vector<int> &result) {
if (root == nullptr) return;
std::queue<const BinaryTreeNode<int>*> q;
q.push(root);
while (!q.empty()) {
auto node = q.front();
q.pop();
result.push_back(node->data);
if (node->left) q.push(node->left.get());
if (node->right) q.push(node->right.get());
}
}应用场景:
- 查找某一层的所有节点
- 计算树的高度
- 判断是否为完全二叉树
遍历序列的性质
唯一确定一棵二叉树
| 给定序列 | 能否唯一确定二叉树 |
|---|---|
| 前序 + 中序 | ✓ 可以唯一确定 |
| 后序 + 中序 | ✓ 可以唯一确定 |
| 前序 + 后序 | ✗ 不能唯一确定(缺少中序信息) |
示例:给定前序序列 [A, B, D, E, C, F, G] 和中序序列 [D, B, E, A, F, C, G],可以唯一重建二叉树:
- 前序第一个元素 A 是根节点
- 中序中 A 左边的
[D, B, E]是左子树,右边的[F, C, G]是右子树 - 递归处理左右子树
森林与二叉树遍历的关系
对于森林和对应二叉树的遍历序列:
| 森林遍历 | 对应二叉树遍历 |
|---|---|
| 前序遍历 | 前序遍历 |
| 后序遍历 | 中序遍历 |
Baseline 任务: 二叉树的遍历
任务概述
本模块包含以下任务,学生需要按照难度逐步完成:
| 任务编号 | 任务名称 | 难度 | 必须完成 |
|---|---|---|---|
| T1 | 前序遍历(递归) | ⭐ 基础 | ✓ |
| T2 | 中序遍历(递归) | ⭐ 基础 | ✓ |
| T3 | 后序遍历(递归) | ⭐ 基础 | ✓ |
| T4 | 层序遍历 | ⭐ 基础 | ✓ |
| T5 | 前序遍历(非递归) | ⭐⭐ 进阶 | ○ |
| T6 | 中序遍历(非递归) | ⭐⭐ 进阶 | ○ |
| T7 | 后序遍历(非递归) | ⭐⭐ 进阶 | ○ |
| T8 | 重建二叉树 | ⭐⭐ 进阶 | ✓ |
| T9 | 判断对称二叉树 | ⭐⭐ 进阶 | ○ |
| T10 | 遍历输出某一层 | ⭐⭐ 进阶 | ○ |
| T11 | 计算树的最大宽度 | ⭐⭐⭐ 挑战 | ○ |
| T12 | Morris 遍历 | ⭐⭐⭐ 挑战 | ○ |
⭐ 基础任务
T1: 前序遍历(递归)
任务描述:使用递归方式实现二叉树的前序遍历。
函数签名:
cpp
void preorder_recursive(const BinaryTreeNode<int> *root, std::vector<int> &result);遍历顺序:根节点 → 左子树 → 右子树
输入要求:
root:二叉树的根节点指针result:用于存储遍历结果的向量(输出参数)
输出要求:
- 将遍历结果按顺序存入
result向量
示例:
cpp
// 输入二叉树:
// 1
// / \\
// 2 3
// / \ / \\
// 4 5 6 7
// 输出: {1, 2, 4, 5, 3, 6, 7}提示:
- 先访问当前节点,再递归访问左子树,最后递归访问右子树
- 注意处理空指针情况
T2: 中序遍历(递归)
任务描述:使用递归方式实现二叉树的中序遍历。
函数签名:
cpp
void inorder_recursive(const BinaryTreeNode<int> *root, std::vector<int> &result);遍历顺序:左子树 → 根节点 → 右子树
示例:
cpp
// 输入二叉树:
// 1
// / \\
// 2 3
// / \ / \\
// 4 5 6 7
// 输出: {4, 2, 5, 1, 6, 3, 7}应用场景:对于二叉搜索树,中序遍历结果为有序序列。
T3: 后序遍历(递归)
任务描述:使用递归方式实现二叉树的后序遍历。
函数签名:
cpp
void postorder_recursive(const BinaryTreeNode<int> *root, std::vector<int> &result);遍历顺序:左子树 → 右子树 → 根节点
示例:
cpp
// 输入二叉树:
// 1
// / \\
// 2 3
// / \ / \\
// 4 5 6 7
// 输出: {4, 5, 2, 6, 7, 3, 1}应用场景:用于删除二叉树(先删除子节点再删除根节点)。
T4: 层序遍历
任务描述:实现二叉树的层序遍历(广度优先遍历)。
函数签名:
cpp
void level_order(const BinaryTreeNode<int> *root, std::vector<int> &result);遍历顺序:按层次从上到下、从左到右依次访问节点。
示例:
cpp
// 输入二叉树:
// 1
// / \\
// 2 3
// / \ / \\
// 4 5 6 7
// 输出: {1, 2, 3, 4, 5, 6, 7}提示:
- 使用队列(
std::queue)辅助实现 - 根节点入队,循环处理:出队访问,左右子节点入队
⭐⭐ 进阶任务
T5: 前序遍历(非递归)
任务描述:使用栈实现前序遍历的迭代版本。
函数签名:
cpp
void preorder_iterative(const BinaryTreeNode<int> *root, std::vector<int> &result);提示:
- 使用
std::stack存储待访问节点 - 入栈顺序:右孩子先入栈,左孩子后入栈
T6: 中序遍历(非递归)
任务描述:使用栈实现中序遍历的迭代版本。
函数签名:
cpp
void inorder_iterative(const BinaryTreeNode<int> *root, std::vector<int> &result);提示:
- 先将所有左孩子入栈
- 出栈访问后,转向右孩子
T7: 后序遍历(非递归)
任务描述:使用栈实现后序遍历的迭代版本。
函数签名:
cpp
void postorder_iterative(const BinaryTreeNode<int> *root, std::vector<int> &result);提示:
- 可以使用双栈方法
- 或使用单栈+前驱节点标记法
T8: 重建二叉树
任务描述:根据前序遍历和中序遍历序列重建二叉树。
函数签名:
cpp
std::unique_ptr<BinaryTreeNode<char>> build_tree_from_pre_in(
const std::vector<char> &preorder,
const std::vector<char> &inorder);输入要求:
preorder:前序遍历序列inorder:中序遍历序列
输出要求:
- 返回重建后的二叉树根节点
示例:
cpp
// 输入: preorder = {A, B, D, E, C, F, G}
// inorder = {D, B, E, A, F, C, G}
// 输出: 重建如下二叉树
// A
// / \\
// B C
// / \ / \\
// D E F G算法步骤:
- 前序序列第一个元素为根节点
- 在中序序列中找到根节点位置,左边为左子树,右边为右子树
- 递归处理左右子树
T9: 判断对称二叉树
任务描述:判断一棵二叉树是否对称(左右镜像相同)。
函数签名:
cpp
bool is_symmetric(const BinaryTreeNode<int> *root);示例:
cpp
// 输入: 对称二叉树
// 1
// / \\
// 2 2
// / \ / \\
// 3 4 4 3
// 输出: true提示:
- 比较左子树的左孩子与右子树的右孩子
- 比较左子树的右孩子与右子树的左孩子
T10: 遍历输出某一层
任务描述:输出二叉树第 k 层的所有节点。
函数签名:
cpp
void print_level(const BinaryTreeNode<int> *root, int k, std::vector<int> &result);输入要求:
root:二叉树的根节点k:层数(第 1 层为根节点层)
示例:
cpp
// 输入: k=3
// 1
// / \\
// 2 3
// / \ / \\
// 4 5 6 7
// 输出: {4, 5, 6, 7}⭐⭐⭐ 挑战任务
T11: 计算树的最大宽度
任务描述:计算二叉树的最大宽度(节点数最多的一层的节点数)。
函数签名:
cpp
int max_width(const BinaryTreeNode<int> *root);提示:
- 使用层序遍历记录每层节点数
- 或使用带编号的层序遍历
T12: Morris 遍历
任务描述:实现空间复杂度为 O(1) 的 Morris 中序遍历。
函数签名:
cpp
void morris_inorder(BinaryTreeNode<int> *root, std::vector<int> &result);提示:
- 利用空闲指针临时建立线索
- 不使用栈,只使用常量空间
数据结构定义
本项目中使用以下数据结构(定义在 traversal.h):
cpp
template <typename T>
struct BinaryTreeNode {
T data;
std::unique_ptr<BinaryTreeNode> left;
std::unique_ptr<BinaryTreeNode> right;
};实现要求
必须实现的遍历函数
cpp
// 前序遍历(递归)
void preorder_recursive(const BinaryTreeNode<int> *root, std::vector<int> &result);
// 中序遍历(递归)
void inorder_recursive(const BinaryTreeNode<int> *root, std::vector<int> &result);
// 后序遍历(递归)
void postorder_recursive(const BinaryTreeNode<int> *root, std::vector<int> &result);
// 层序遍历
void level_order(const BinaryTreeNode<int> *root, std::vector<int> &result);可选实现的遍历函数
cpp
// 前序遍历(非递归)
void preorder_iterative(const BinaryTreeNode<int> *root, std::vector<int> &result);
// 中序遍历(非递归)
void inorder_iterative(const BinaryTreeNode<int> *root, std::vector<int> &result);
// 后序遍历(非递归)
void postorder_iterative(const BinaryTreeNode<int> *root, std::vector<int> &result);示例代码
cpp
#include "traversal.h"
int main() {
// 创建一棵二叉树
auto root = BinaryTreeNode<int>::get_new_node(1);
root->left = BinaryTreeNode<int>::get_new_node(2);
root->right = BinaryTreeNode<int>::get_new_node(3);
root->left->left = BinaryTreeNode<int>::get_new_node(4);
root->left->right = BinaryTreeNode<int>::get_new_node(5);
root->right->left = BinaryTreeNode<int>::get_new_node(6);
root->right->right = BinaryTreeNode<int>::get_new_node(7);
std::vector<int> result;
// 前序遍历: 1 2 4 5 3 6 7
preorder_recursive(root.get(), result);
std::cout << "前序遍历: ";
for (int val : result) std::cout << val << " ";
std::cout << std::endl;
result.clear();
// 中序遍历: 4 2 5 1 6 3 7
inorder_recursive(root.get(), result);
std::cout << "中序遍历: ";
for (int val : result) std::cout << val << " ";
std::cout << std::endl;
result.clear();
// 后序遍历: 4 5 2 6 7 3 1
postorder_recursive(root.get(), result);
std::cout << "后序遍历: ";
for (int val : result) std::cout << val << " ";
std::cout << std::endl;
result.clear();
// 层序遍历: 1 2 3 4 5 6 7
level_order(root.get(), result);
std::cout << "层序遍历: ";
for (int val : result) std::cout << val << " ";
std::cout << std::endl;
return 0;
}提交要求
- 所有任务代码需放在
Tree/Baseline/traversal目录下 - 每个任务需编写对应的测试文件
- 基础任务(T1-T4)必须全部完成
- 任务 T8(重建二叉树)必须完成
- 进阶任务(T5-T7, T9-T10)至少完成 2 个
- 挑战任务(T11-T12)可选完成
测试验证
建议编写以下测试用例验证代码正确性:
cpp
// 创建标准测试二叉树
auto create_test_tree() {
auto root = BinaryTreeNode<int>::get_new_node(1);
root->left = BinaryTreeNode<int>::get_new_node(2);
root->right = BinaryTreeNode<int>::get_new_node(3);
root->left->left = BinaryTreeNode<int>::get_new_node(4);
root->left->right = BinaryTreeNode<int>::get_new_node(5);
root->right->left = BinaryTreeNode<int>::get_new_node(6);
root->right->right = BinaryTreeNode<int>::get_new_node(7);
return root;
}
// 测试函数
void test_traversal() {
auto root = create_test_tree();
std::vector<int> result;
preorder_recursive(root.get(), result);
assert(result == std::vector<int>{1, 2, 4, 5, 3, 6, 7});
result.clear();
inorder_recursive(root.get(), result);
assert(result == std::vector<int>{4, 2, 5, 1, 6, 3, 7});
result.clear();
postorder_recursive(root.get(), result);
assert(result == std::vector<int>{4, 5, 2, 6, 7, 3, 1});
result.clear();
level_order(root.get(), result);
assert(result == std::vector<int>{1, 2, 3, 4, 5, 6, 7});
}