考研数据结构之二叉树(二):二叉树的遍历与线索二叉树
在上一篇文章中,我们详细探讨了二叉树的定义和存储结构。本文将深入讲解二叉树的核心操作——遍历,以及其重要应用形式之一——线索二叉树。这些内容不仅是考研的重点,也是实际编程中常用的重要知识点。
下期预告:后续文章将进一步探讨二叉树的其他高级应用(如森林、平衡二叉树、哈夫曼树等)
一、二叉树的遍历
1. 遍历的概念
遍历是指按照某种规则访问二叉树中的每个节点且仅访问一次的过程。根据访问根节点的顺序不同,遍历分为以下三种基本方式:
-
先序遍历(Pre-order Traversal)
访问顺序:根节点 → 左子树 → 右子树。
特点:可以用来复制二叉树或构造表达式树。 -
中序遍历(In-order Traversal)
访问顺序:左子树 → 根节点 → 右子树。
特点:对于二叉搜索树,中序遍历结果是有序的。 -
后序遍历(Post-order Traversal)
访问顺序:左子树 → 右子树 → 根节点。
特点:常用于释放二叉树内存(如删除整棵树)。
此外,还有一种特殊的遍历方式——层次遍历(Level-order Traversal),也称广度优先遍历(BFS)。它按照从上到下、从左到右的顺序逐层访问节点。
2. 遍历的实现
(1)递归实现
递归实现简单直观,直接按照遍历顺序编写代码。以C语言为例:
void PreOrder(BiTree T) {
if (T) {
printf("%d ", T->data); // 访问根节点
PreOrder(T->lchild); // 递归访问左子树
PreOrder(T->rchild); // 递归访问右子树
}
}
void InOrder(BiTree T) {
if (T) {
InOrder(T->lchild); // 递归访问左子树
printf("%d ", T->data); // 访问根节点
InOrder(T->rchild); // 递归访问右子树
}
}
void PostOrder(BiTree T) {
if (T) {
PostOrder(T->lchild); // 递归访问左子树
PostOrder(T->rchild); // 递归访问右子树
printf("%d ", T->data); // 访问根节点
}
}
(2)非递归实现
非递归实现通常借助栈完成。例如,中序遍历的非递归实现如下:
void InOrderNonRecursive(BiTree T) {
Stack S;
InitStack(S);
BiTree p = T;
while (p || !IsEmpty(S)) {
if (p) {
Push(S, p); // 当前节点入栈
p = p->lchild; // 继续访问左子树
} else {
Pop(S, &p); // 弹出栈顶元素
printf("%d ", p->data); // 访问节点
p = p->rchild; // 转向右子树
}
}
}
(3)层次遍历
层次遍历使用队列实现,按照“先进先出”原则逐层访问节点。
void LevelOrder(BiTree T) {
Queue Q;
InitQueue(Q);
EnQueue(Q, T);
while (!IsEmpty(Q)) {
BiTree node;
DeQueue(Q, &node);
printf("%d ", node->data); // 访问当前节点
if (node->lchild) EnQueue(Q, node->lchild);
if (node->rchild) EnQueue(Q, node->rchild);
}
}
二、线索二叉树
1. 线索二叉树的定义
普通二叉树中,空指针域(NULL
)未被利用。为了提高遍历效率,可以通过线索化将空指针指向某些特定节点(如前驱或后继),从而减少遍历时对栈的依赖。这种经过改造的二叉树称为线索二叉树。
线索二叉树分为以下几种类型:
- 先序线索二叉树:按先序遍历规则线索化。
- 中序线索二叉树:按中序遍历规则线索化。
- 后序线索二叉树:按后序遍历规则线索化。
2. 线索二叉树的存储结构
为实现线索化,需在节点结构中增加两个标志位字段:
typedef struct ThreadNode {
int data;
struct ThreadNode *lchild, *rchild;
int ltag, rtag; // 0表示指针指向子节点,1表示指向前驱或后继
} ThreadNode, *ThreadTree;
- ltag = 0:
lchild
指向左子节点; - ltag = 1:
lchild
指向前驱节点; - rtag = 0:
rchild
指向右子节点; - rtag = 1:
rchild
指向后继节点。
3. 中序线索化算法
以下是中序线索化的实现:
ThreadNode *pre = NULL; // 全局变量,记录当前节点的前驱
void InThread(ThreadTree T) {
if (T) {
InThread(T->lchild); // 递归线索化左子树
if (!T->lchild) { // 若左子树为空
T->ltag = 1; // 设置ltag为1
T->lchild = pre; // 左指针指向前驱
}
if (pre && !pre->rchild) { // 若前驱的右子树为空
pre->rtag = 1; // 设置rtag为1
pre->rchild = T; // 前驱的右指针指向当前节点
}
pre = T; // 更新前驱为当前节点
InThread(T->rchild); // 递归线索化右子树
}
}
三、真题解析
1. 遍历序列还原二叉树
题目(2021年真题,):
已知某二叉树的先序序列为
ABDCE
,中序序列为DBAEC
,请画出该二叉树并写出后序序列。
解析:
- 先序:
A
为根节点,左子树先序为BD
,右子树先序为CE
。 - 中序:
A
左侧为左子树DB
,右侧为右子树EC
。 - 递归构建:
A / \ B C / / D E
- 后序序列:
D B E C A
。
2. 层次遍历的应用
题目:
已知二叉树的层次遍历序列为
A B C D E # #
,请写出其中序遍历序列。
解析:
- 还原二叉树:
A / \ B C / \ D E
- 中序遍历:
D B E A C
。
四、总结
- 遍历是二叉树的核心操作,掌握递归与非递归实现方法至关重要。
- 线索二叉树通过利用空指针提升遍历效率,是一种重要的优化手段。