【考研408数据结构-06】 树与二叉树(上):遍历算法全解析
📚 【考研408数据结构-06】 树与二叉树(上):遍历算法全解析
🎯 考频:⭐⭐⭐⭐⭐ | 题型:选择题、综合应用题、算法设计题 | 分值:约10-18分
引言
想象你正在整理家谱,从祖父母开始,一代代向下延伸,每个人都可能有多个子女——这就是树形结构最直观的体现。而如果限制每个人最多只能有两个孩子,这就成了计算机科学中最重要的数据结构之一:二叉树(Binary Tree)。
在408考试中,二叉树是绝对的重中之重,近5年真题中平均每年出现3-4道相关题目,尤其是遍历算法和根据遍历序列构造二叉树,几乎年年必考。掌握二叉树,就掌握了数据结构的半壁江山。
本文将带你深入理解二叉树的本质,掌握四种遍历方式的递归与非递归实现,攻克408考试中的遍历算法难题。
学完本文,你将能够:
- ✅ 深刻理解二叉树的五大性质
- ✅ 熟练编写四种遍历的递归与非递归代码
- ✅ 快速根据遍历序列构造二叉树
- ✅ 掌握线索二叉树的精髓
一、知识精讲
1.1 概念定义
二叉树的定义
**二叉树(Binary Tree)**是n(n≥0)个结点的有限集合,该集合或者为空集(空二叉树),或者由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树的二叉树组成。
💡 关键特征:
- 每个结点最多有两个孩子(可以是0、1或2个)
- 左右子树是有序的,不能互换
- 即使只有一个孩子,也要区分是左孩子还是右孩子
408考纲要求:⭐ 掌握
特殊二叉树
- 满二叉树:所有分支结点都有左右子树,且所有叶子都在同一层
- 完全二叉树:最后一层的结点都集中在左侧连续位置
- 二叉排序树:左子树值 < 根 < 右子树值
- 平衡二叉树:任意结点的左右子树高度差不超过1
⚠️ 易混淆点:完全二叉树一定是满二叉树吗?不是!满二叉树一定是完全二叉树。
1.2 原理分析
二叉树的五大性质(408必背)
性质 | 内容 | 重要程度 |
---|---|---|
性质1 | 第i层最多有2i−12^{i-1}2i−1个结点 (i≥1) | ⭐⭐⭐⭐⭐ |
性质2 | 深度为k的二叉树最多有2k−12^k-12k−1个结点 | ⭐⭐⭐⭐⭐ |
性质3 | n个结点的完全二叉树深度为⌊log2n⌋+1\lfloor\log_2n\rfloor+1⌊log2n⌋+1 | ⭐⭐⭐⭐⭐ |
性质4 | 任何二叉树,n0=n2+1n_0 = n_2 + 1n0=n2+1(叶子数=度为2的结点数+1) | ⭐⭐⭐⭐⭐ |
性质5 | 完全二叉树结点编号规律:左孩子2i,右孩子2i+1 | ⭐⭐⭐⭐ |
四种遍历方式的本质
遍历就是按某种次序访问树中的每个结点恰好一次。根据访问根结点的时机不同,分为:
- 先序遍历(PreOrder):根→左→右
- 中序遍历(InOrder):左→根→右
- 后序遍历(PostOrder):左→右→根
- 层序遍历(LevelOrder):按层次从上到下、从左到右
🎯 核心理解:前三种是深度优先(DFS),层序是广度优先(BFS)
1.3 性质与特点
存储结构对比
存储方式 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
顺序存储 | 完全二叉树 | 结点关系计算简单 | 非完全二叉树浪费空间 |
链式存储 | 一般二叉树 | 灵活,空间利用率高 | 需要额外指针空间 |
遍历算法复杂度
遍历方式 | 时间复杂度 | 空间复杂度(递归) | 空间复杂度(非递归) |
---|---|---|---|
先序遍历 | O(n) | O(h) | O(h) |
中序遍历 | O(n) | O(h) | O(h) |
后序遍历 | O(n) | O(h) | O(h) |
层序遍历 | O(n) | - | O(w) |
其中:h为树高,w为树的最大宽度
二、代码实现
2.1 二叉树的结构定义与基本操作
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>// 二叉树结点的定义
typedef struct BiTNode {int data; // 数据域struct BiTNode *lchild; // 左孩子指针struct BiTNode *rchild; // 右孩子指针
} BiTNode, *BiTree;// 创建新结点
BiTNode* CreateNode(int data) {BiTNode *node = (BiTNode*)malloc(sizeof(BiTNode));if (node != NULL) {node->data = data;node->lchild = NULL;node->rchild = NULL;}return node;
}
2.2 四种遍历的递归实现
// 先序遍历(递归)- 时间O(n),空间O(h)
void PreOrder(BiTree T) {if (T != NULL) {printf("%d ", T->data); // 访问根结点PreOrder(T->lchild); // 递归遍历左子树PreOrder(T->rchild); // 递归遍历右子树}
}// 中序遍历(递归)- 时间O(n),空间O(h)
void InOrder(BiTree T) {if (T != NULL) {InOrder(T->lchild); // 递归遍历左子树printf("%d ", T->data); // 访问根结点InOrder(T->rchild); // 递归遍历右子树}
}// 后序遍历(递归)- 时间O(n),空间O(h)
void PostOrder(BiTree T) {if (T != NULL) {PostOrder(T->lchild); // 递归遍历左子树PostOrder(T->rchild); // 递归遍历右子树printf("%d ", T->data); // 访问根结点}
}
2.3 非递归遍历实现(408高频考点)
#define MAXSIZE 100// 栈的简单实现(用于非递归遍历)
typedef struct {BiTNode* data[MAXSIZE];int top;
} SqStack;// 中序遍历(非递归)- 最重要!408必考
void InOrderNonRecursive(BiTree T) {SqStack S;S.top = -1; // 初始化栈BiTNode *p = T;while (p != NULL || S.top != -1) {if (p != NULL) {// 一路向左,沿途结点入栈S.data[++S.top] = p; p = p->lchild;} else {// 左子树访问完毕,弹出栈顶并访问p = S.data[S.top--];printf("%d ", p->data);// 转向右子树p = p->rchild;}}
}// 层序遍历 - 使用队列实现
void LevelOrder(BiTree T) {if (T == NULL) return;BiTNode* queue[MAXSIZE];int front = 0, rear = 0;queue[rear++] = T; // 根结点入队while (front < rear) {BiTNode *p = queue[front++]; // 出队printf("%d ", p->data); // 访问// 左右孩子依次入队if (p->lchild != NULL) queue[rear++] = p->lchild;if (p->rchild != NULL) queue[rear++] = p->rchild;}
}
2.4 根据遍历序列构造二叉树(408必考算法)
// 根据先序和中序序列构造二叉树
// pre[]为先序序列,in[]为中序序列,n为结点数
BiTree BuildTree(int pre[], int in[], int n) {if (n <= 0) return NULL;// 先序第一个元素是根BiTNode *root = CreateNode(pre[0]);// 在中序中找到根的位置int i;for (i = 0; i < n; i++) {if (in[i] == pre[0]) break;}// 递归构造左右子树// 左子树:先序[1...i],中序[0...i-1]root->lchild = BuildTree(pre + 1, in, i);// 右子树:先序[i+1...n-1],中序[i+1...n-1]root->rchild = BuildTree(pre + i + 1, in + i + 1, n - i - 1);return root;
}
三、图解说明
【图1】四种遍历方式对比
示例二叉树:1/ \2 3/ \4 5遍历顺序演示:
先序(根左右):1 → 2 → 4 → 5 → 3
中序(左根右):4 → 2 → 5 → 1 → 3
后序(左右根):4 → 5 → 2 → 3 → 1
层序(逐层) :1 → 2 → 3 → 4 → 5
【图2】中序非递归遍历执行过程
步骤1:初始化,p指向根1
栈:[ ] 输出:步骤2:1入栈,p指向2
栈:[1] 输出:步骤3:2入栈,p指向4
栈:[1,2] 输出:步骤4:4入栈,p指向NULL
栈:[1,2,4] 输出:步骤5:4出栈并访问,p指向NULL
栈:[1,2] 输出:4步骤6:2出栈并访问,p指向5
栈:[1] 输出:4 2步骤7:5入栈,p指向NULL
栈:[1,5] 输出:4 2步骤8:5出栈并访问,p指向NULL
栈:[1] 输出:4 2 5步骤9:1出栈并访问,p指向3
栈:[ ] 输出:4 2 5 1步骤10:3入栈并最终访问
栈:[ ] 输出:4 2 5 1 3
【图3】根据遍历序列构造二叉树
已知:先序 ABDEC,中序 DBEAC
构造过程:Step 1: A是根(先序首元素)A/ \左子树 右子树DBE CStep 2: 递归处理左子树先序:BDE,中序:DBEB是根,D在左,E在右A/B/ \D EStep 3: 递归处理右子树先序:C,中序:CA/ \B C/ \D E
四、真题演练
【2023年408真题】
题目:一棵非空的二叉树的先序遍历序列与后序遍历序列正好相反,则该二叉树一定满足什么条件?
解题思路:
- 先序:根→左→右
- 后序:左→右→根
- 若先序与后序相反,说明结构特殊
- 分析:只有当二叉树每层只有一个结点时才满足
答案:该二叉树的所有非叶结点都只有一个孩子(即为一条链)
⚠️ 易错点:容易误认为是满二叉树或完全二叉树
【2022年408真题·改编】
题目:已知一棵二叉树的中序遍历序列为DGBAECF,后序遍历序列为GDBEFCA,求先序遍历序列。
解题思路:
- 后序最后一个是根:A
- 中序中A的位置:DGB|A|ECF
- 左子树:中序DGB,后序GDB → B是根
- 右子树:中序ECF,后序EFC → C是根
- 递归处理得完整结构
答案:先序遍历序列为ABDGCEF
举一反三:如果只给出先序和后序,能唯一确定二叉树吗?不能!除非是满二叉树。
【经典考点】
题目:n个不同元素进栈,可能的出栈序列有多少种?
答案:卡特兰数 Cn=1n+1C2nnC_n = \frac{1}{n+1}C_{2n}^nCn=n+11C2nn
这与二叉树的关系:n个结点的不同形态二叉树数目也是卡特兰数!
五、在线练习推荐
LeetCode精选题目
- 🟢 Easy: 144. 二叉树的前序遍历
- 🟢 Easy: 94. 二叉树的中序遍历(重点练习非递归)
- 🟡 Medium: 105. 从前序与中序遍历序列构造二叉树(408必做)
- 🟡 Medium: 102. 二叉树的层序遍历
练习顺序建议
- 先掌握四种递归遍历
- 重点突破中序非递归
- 练习根据序列构造二叉树
- 综合应用:遍历的各种变形题
推荐在牛客网完成"408二叉树专项",共40道精选题。
六、思维导图
树与二叉树:遍历算法全解析
├── 基础概念
│ ├── 二叉树定义
│ │ ├── 递归定义
│ │ └── 左右子树有序
│ ├── 特殊二叉树
│ │ ├── 满二叉树
│ │ ├── 完全二叉树⭐
│ │ └── 二叉排序树
│ └── 五大性质⭐⭐⭐
│ ├── 第i层最多2^(i-1)个
│ └── n0 = n2 + 1
├── 存储结构
│ ├── 顺序存储
│ │ └── 适合完全二叉树
│ └── 链式存储
│ └── 二叉链表
├── 遍历算法⭐⭐⭐⭐⭐
│ ├── DFS遍历
│ │ ├── 先序(根左右)
│ │ ├── 中序(左根右)
│ │ └── 后序(左右根)
│ ├── BFS遍历
│ │ └── 层序遍历
│ └── 实现方式
│ ├── 递归实现
│ └── 非递归实现⭐
└── 应用├── 构造二叉树⭐⭐⭐├── 表达式树└── 线索二叉树
七、复习清单
✅ 本章必背知识点清单
概念理解
- 能准确说出二叉树与树的区别
- 理解满二叉树与完全二叉树的区别
- 掌握二叉树的五大性质
- 记住完全二叉树的编号规律
代码实现
- 能手写四种遍历的递归代码
- 能手写中序遍历的非递归代码⭐
- 能手写层序遍历代码
- 掌握根据两种序列构造二叉树
- 时间复杂度均为 O(n)
- 空间复杂度为 O(h)或O(w)
应用能力
- 会根据遍历序列画出二叉树
- 能判断给定序列是否为某种遍历
- 掌握遍历序列的唯一性条件
- 理解卡特兰数与二叉树的关系
真题要点
- 记住:中序+先序/后序可唯一确定二叉树
- 掌握非递归遍历的栈操作顺序
- 理解线索二叉树的作用
- 记住常见陷阱:先序+后序不能唯一确定
八、知识拓展
工程实践中的应用
- 编译器:语法树的构建与遍历
- 数据库:B+树的遍历实现索引扫描
- 人工智能:决策树的构建与剪枝
- 游戏开发:场景图的渲染顺序控制
常见误区
⚠️ 误区1:认为二叉树就是度为2的树
- 正解:二叉树的结点度可以是0、1或2
⚠️ 误区2:混淆遍历的访问顺序
- 正解:先中后指的是根的访问时机,不是方向
⚠️ 误区3:认为只有递归才能实现遍历
- 正解:所有递归都可以用栈改写为非递归
⚠️ 误区4:忽视空树的处理
- 正解:空树是合法的二叉树,代码要处理NULL
记忆技巧
🎵 遍历口诀:
- “先序根在前,中序根在中,后序根在后”
- “层序队列做,递归栈中走”
- “中序加一序,二叉树唯一”
自测题
-
一棵有n个结点的完全二叉树,其叶子结点数为?
- A. ⌊n/2⌋
- B. ⌈n/2⌉
- C. ⌊(n+1)/2⌋
- D. ⌈(n+1)/2⌉
-
中序遍历二叉排序树得到的序列是?
- A. 递增序列
- B. 递减序列
- C. 无序序列
- D. 先增后减
-
已知二叉树的先序序列和后序序列,能唯一确定这棵二叉树吗?
- A. 一定能
- B. 一定不能
- C. 当为满二叉树时能
- D. 当为完全二叉树时能
答案:1.B 2.A 3.C
结语
二叉树的遍历是数据结构的核心基础,它不仅是408考试的必考重点,更是理解高级树形结构(BST、AVL、B树等)的基石。本文从基础概念到代码实现,从递归到非递归,全面解析了二叉树遍历的精髓。
核心要点回顾:
- 🎯 二叉树的五大性质是解题的数学基础
- 🎯 四种遍历方式各有特点和应用场景
- 🎯 非递归遍历是理解栈应用的绝佳案例
- 🎯 根据遍历序列构造二叉树是408的高频考点
- 🎯 中序+另一序列可唯一确定二叉树
遍历是手段,应用是目的。下一篇文章,我们将深入探讨**《树与二叉树(下):特殊树结构与应用》**,学习二叉排序树、平衡二叉树、哈夫曼树等高级内容,这些都是在遍历基础上的深化与应用。
💪 学习建议:二叉树的遍历看似简单,但变化无穷。建议你不仅要会写代码,更要理解其背后的递归思想和栈的应用。特别是非递归遍历,一定要自己动手画图模拟执行过程。记住,树形思维是算法进阶的关键!
相信自己,408高分就在前方!🎯