二叉树
定义
- 一个有穷的结点集合
- 这个集合可以为空
- 若不为空,则它是由根结点和称为其左子树
TL 和右子树TR 的两个不相交的二叉树组成。 - 二叉树具体五种基本形态
- 二叉树的子树有左右顺序之分
特殊二叉树
斜二叉树(Skewed Binary Tree)
满二叉树(Full Binary Tree)
又称完美二叉树(Perfect Binary Tree)
完全二叉树(Complete Binary Tree)
有
非完全二叉树
二叉树的性质
- 一个二叉树第
i 层的最大结点数为:2i−1 ,i≥1 - 深度为
k 的二叉树有最大结点总数为:2k−1 ,k≥1 对任何非空二叉树T,若
n0 表示叶结点的个数、n1 是度为1的非叶结点个数、n2 是度为2的非叶结点个数,那么两者满足关系n0=n2+1
例:n0=4 ,n1=2 n2=3 - 所以可以推出
n0=n2+1
抽象数据类型定义
- 类型名称:二叉树
- 数据对象集:一个有穷的结点集合。若不为空,则由根结点和其左右二叉子树组成
- 操作集:
BT∈BinTree ,Item∈ElementType ,重要的操作有:
Boolean IsEmpty(BinTree BT)
:判断BT是否为空BinTree CreatBinTree()
:创建一个二叉树void Traversal(BinTree BT)
:遍历,按某顺序访问每个结点
void PreOrderTraversal(BinTree BT)
:先序遍历—根、左子树、右子树void InOrderTraversal(BinTree BT)
:中序遍历—左子树、根、右子树void PostOrderTraversal(BinTree BT)
:后序遍历—左子树、右子树、根void LevelOrderTraversal(BinTree BT)
:层序遍历—从上到下、从左到右
顺序存储结构
完全二叉树
按从上至下、从左到右顺序存储
* 非根结点(序号
* 结点(序号为
* 结点(序号为
例
结点 | 序号 |
---|---|
A | 1 |
B | 2 |
O | 3 |
C | 4 |
S | 5 |
M | 6 |
Q | 7 |
W | 8 |
K | 9 |
一般二叉树
一般二叉树也可以采用这种结果,但会造成空间浪费,不推荐使用
例
结点 | 序号 |
---|---|
A | 1 |
B | 2 |
O | 3 |
NULL | 4 |
NULL | 5 |
M | 6 |
NULL | 7 |
NULL | 8 |
NULL | 9 |
NULL | 10 |
NULL | 11 |
NULL | 12 |
C | 13 |
链式存储
结构定义
typedef struct TreeNode *BinTree;
typedef BinTree Position;
struct TreeNode {
ElementType Data;
BinTree Left;
BinTree Right;
}
二叉树的递归遍历
先序遍历
遍历过程:
1. 访问根结点
2. 先序遍历其左子树
3. 先序遍历其右子树
实现
void PreOrderTraversal(BinTree BT) {
if (NULL != BT) {
printf("%d ", BT->Data);
PreOrderTraversal(BT->Left);
PreOrderTraversal(BT->Right);
}
}
例
遍历结果:A B D F E C G H I
在整个遍历过程中,根结点在子树遍历时总是第一个输出的
中序遍历
遍历过程:
1. 中序遍历其左子树
2. 访问根结点
3. 中序遍历其右子树
实现
void InOrderTraversal(BinTree BT) {
if (NULL != BT) {
InOrderTraversal(BT->Left);
printf("%d ", BT->Data);
InOrderTraversal(BT->Right);
}
}
例
遍历结果:D B E F A G H C I
中序遍历时,根结点的左子树输出在根结点之前,右子树输出在根结点之后
后序遍历
遍历过程:
1. 后序遍历其左子树
2. 后续遍历其右子树
3. 访问根结点
实现
void PostOrderTraversal(BinTree BT) {
if (NULL != BT) {
PostOrderTraversal(BT->Left);
PostOrderTraversal(BT->Right);
printf("%d ", BT->Data);
}
}
例
遍历结果:D E F B H G I C A
在整个遍历过程中,根结点在子树遍历时总是最后一个输出的
总结
- 先序、中序和后序遍历过程中经过结点的路线一样,只是访问各结点的时机不同
- 图中在从入口到出口的曲线上用
⊗ 、⋆ 和△ 三种符号分别标记出了先序、中序和后序访问各结点的时刻
二叉树的非递归遍历
非递归算法实现的基本思路:使用栈
中序遍历
- 遇到一个结点,就把它压栈
- 当左子树遍历结束后,从栈顶弹出这个结点并访问它
- 然后按其右指针再去中序遍历该结点的右子树
实现
void InOrderTraversal(BinTree BT) {
BinTree T = BT;
Stack S = CreatStack(MaxSize); // 创建并初始化栈S
while (NULL != T || !IsEmpty(S)) {
while (NULL != T) { // 一直向左并将沿途结点压入栈中
Push(S, T);
T = T->Left;
}
T = Pop(S); // 结点弹出栈
printf("%d ", T->Data); // 访问结点
T = T->Right; // 转向右子树
}
}
先序遍历
- 遇到一个结点,先访问结点,然后把它压栈
- 当左子树遍历结束后,从栈顶弹出这个结点但不访问
- 然后按其右指针再去前序遍历该结点的右子树
实现
void PreOrderTraversal(BinTree BT) {
BinTree T = BT;
Stack S = CreatStack(MaxSize); // 创建并初始化栈S
while (NULL != T || !IsEmpty(S)) {
while (NULL != T) { // 一直向左并将沿途结点压入栈中
printf("%d ", T->Data); // 访问结点
Push(S, T);
T = T->Left;
}
T = Pop(S); // 结点弹出栈
T = T->Right; // 转向右子树
}
}
后序遍历
- 后序遍历思路与前序遍历相似,不过是按照根、右子树、左子树的方式遍历以后逆序输出
- 使用两个栈来进行,栈1保存访问的结果,栈2进行遍历
- 遇到一个结点,先把这个结点压入栈1,然后同时压入栈2
- 当右子树遍历结束后,从栈2中弹出这个结点但不访问
- 然后按照其左指针再去遍历该结点的左子树
- 遍历结束以后,输出栈1的值,即为后序遍历的结果
实现
void PostOrderTraversal(BinTree BT) {
BinTree T = BT;
Stack S = CreatStack(MaxSize); // 创建并初始化栈S
Stack Resut_S = CreatStack(MaxSize); // 保存访问结果的栈
while (NULL != T || !IsEmpty(S)) {
while (NULL != T) { // 一直向左并将沿途结点压入栈中
Push(Resut_S, T); // 保存访问结果
Push(S, T);
T = T->Right;
}
T = Pop(S); // 结点弹出栈
T = T->Left; // 转向左子树
}
// 逆序输出
while (!IsEmpty(Resut_S)) {
T = Pop(Resut_S); // 结点弹出栈
printf("%d ", T->Data); // 访问结点
}
}
层序遍历
使用队列来实现,层序基本过程:先根结点入队,然后:
1. 从队列中取出一个元素
2. 访问该元素所指结点
3. 若该元素所指结点的左右孩子结点非空,则将其左右孩子的指针顺序入队列
实现
void LevelOrderTraversal(BinTree BT) {
Queue Q;
BinTree T;
if (NULL == BT) // 如果是空树则直接返回
return;
Q = CreatQueue(MaxSize); // 创建并初始化队列Q
AddQ(Q, BT);
while (!IsEmptyQ(Q)) {
T = DeleteQ(Q);
printf("%d ", T->Data); // 访问取出队列的结点
if (NULL != T->Left)
AddQ(Q, T->Left);
if (NULL != T->Right)
AddQ(Q, T->Right);
}
}
二叉树遍历的应用
输出二叉树中叶子结点
在二叉树的遍历算法中增加检测结点的“左右子树是否都为空”
实现
以前序遍历为例
void PreOrderTraversal(BinTree BT) {
if (NULL != BT) {
if (NULL == BT->Left && NULL == BT->Right)
printf("%d ", BT->Data);
PreOrderTraversal(BT->Left);
PreOrderTraversal(BT->Right);
}
}
求二叉树的高度
实现
int PostOrderGetHeight(BinTree BT) {
int HL, HR, MaxH;
if (NULL != BT) {
HL = PostOrderGetHeight(BT->Left); // 求左子树深度
HR = PostOrderGetHeight(BT->Right); // 求右子树深度
MaxH = (HL > HR) ? HL : HR; // 取左右子树较大的深度
return MaxH + 1; // 返回树的深度
}
return 0; // 空树深度为0
}
二元运算表达式树及其遍历
三种遍历可以得到三种不同的访问结果:
* 先序遍历得到前缀表达式
* 中序遍历得到中缀表达式(中缀表达式会受到运算符优先级的影响,输出的时候不能直接输出,需要做额外处理)
* 后序遍历得到后缀表达式
例
- 先序遍历:
+ + a ∗ b c ∗ + ∗ d e f g - 中序遍历:
a + b ∗ c + d ∗ e + f ∗ g ,这一部分是的运算符优先级是有问题的 - 后序遍历:
a b c ∗ + d e ∗ f + g ∗ +
由两种遍历序列确定二叉树
由中序遍历和前序遍历(后序遍历)可以唯一确定一棵二叉树
例
以先序和中序遍历序列来确定一棵二叉树
- 根据先序遍历序列第一个结点确定根结点
- 根据根结点在中序遍历序列中分割出左右两个子序列
- 对左子树和右子树分别递归使用相同的方法继续分解