目录
前言
本篇总结了有关二叉树的知识点。
二叉树的定义
二叉树T:一个有穷的结点集合。
这个集合可以为空;若不为空则它是由根节点和称为其左子树和右子树的两个不相交的二叉树组成。
二叉树的子树有左右顺序之分
特殊二叉树
- 斜二叉树(Skewed Binary Tree)
- 完美二叉树(Perfect Binary Tree)/满二叉树(Full Binary Tree)
- 完全二叉树(Complete Binary Tree)
有n个结点的二叉树,对树中结点按从上至下、从左到右顺序进行编号,编号为i(1<=i<=n)结点与满二叉树中编号为i结点在二叉树中位置相同。
二叉树的几个重要性质
- 一个二叉树第i层的最大结点数为,i>=1
- 深度为k的二叉树有最大结点总数为:,k>=1
- 对任何非空二叉树T,若表示叶结点的个数、(表示度为1的非叶节点个数) 表示度为2的非叶节点个数,那么满足关系:=+1
二叉树的抽象数据类型定义k
类型名称:二叉树
数据对象集:一个有穷的结点集合。
若不为空,则由根结点和其左、右二叉子树组成。
操作集: BT∈BinTree,Item∈ElementType,重要操作有:
1、Boolean IsEmpty(BinTree BT):判别BT是否为空;
2、void Traversal(BinTree BT):遍历,按某顺序访问每个结点:
3、BinTree CreatBinTree():创建一个二叉树。
常见的遍历方法有:
- void PreOrderTraversal(BinTree BT): 先序——根、左子树、右子树;
- void InOrderTraversal(BinTree BT): 中序——左子树、根、右子树;
- void PostOrderTraversal(BinTree BT): 后序——左子树、右子树、根;
- void LevelOrderTraversal(BinTree BT): 层次遍历,从上到下、从左到右。
二叉树的存储结构
1.顺序存储结构
有一种树用数组实现非常方便。完全二叉树:按从上至下、从左至右顺序存储n个结点的完全二叉树的结点父子关系
结点 | A | B | O | C | S | M | Q | W | K |
序号 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
- 非根节点(序号i>1)的父结点的序号是[i/2]
- 结点(序号为i)的左孩子结点的序号是2i,(若2i<=n,否则没有左孩子)
- 结点(序号为i)的右孩子结点的序号是2i+1,(若2i+1<=n,否则没有右孩子)
一般二叉树也可以采用这种结构(用 空结点 将其补成完全二叉树),但会造成空间浪费
2.链表存储
typedef struct TreeNode *BinTree;
typedef BinTree Position;
struct TreeNode{
ElementType Data;
BinTree Left;
BinTree Right;
};
二叉树的遍历
1.先序遍历
遍历过程为:访问根结点->先序遍历其左子树->先序遍历其右子树。
这样的一种遍历过程实际上也是一种递归,要先序遍历这个树就变成递归的遍历左子树与递归的遍历右子树,所以自然的想到用递归程序来实现。对应的递归程序:
void PreOrderTraversal(BinTree BT)
{
if(BT){
ptintf("%d",BT->Data); //先访问根结点
PreOrderTraversal(BT->Left); //对左子树进行递归
PreOrderTraversal(BT->Right); //对右子树进行递归
}
}
(D B E F) A (G H C I)
2.中序遍历
遍历过程为:中序遍历其左子树->访问根结点->中序遍历其右子树。
void PreOrderTraversal(BinTree BT)
{
if(BT){
InOrderTraversal(BT->Left);
ptintf("%d",BT->Data);
InOrderTraversal(BT->Right);
}
}
G的左边是空的,然后是根(即G),然后是右子树(H)
3.后序遍历
遍历过程为:后序遍历其左子树->后序遍历其右子树->访问根结点。
void PreOrderTraversal(BinTree BT)
{
if(BT){
InOrderTraversal(BT->Left);
InOrderTraversal(BT->Right);
ptintf("%d",BT->Data);
}
}
(D E F B)(H G I C) A
先序、中序和后序遍历过程:遍历过程中经过结点的路线一样,只是访问各结点的时间不同。
先序、中序、后序的遍历方法都是用递归来实现,递归根本的实现方法还是用堆栈。
4.二叉树的非递归遍历
中序遍历非递归遍历算法
非递归算法实现的基本思路:使用堆栈
- 遇到一个结点,就把它压栈,并去遍历它的左子树;
- 当左子树遍历结束后,从栈顶弹出这个结点并访问它;
- 然后按其右指针再去中序遍历该结点的右子树
void InOrderTraversal(BinTree BT)
{
BinTree T=BT;
Stack S=CreatStack(MaxSize); //创建并初始化堆栈S
while( T || !IsEmpty(S)){
while(T){ //一直向左并将沿途结点压入堆栈
push(S,T);
T=T->Left;
}
if(!IsEmpty(S)){
T=Pop(S); //结点弹出堆栈
printf("%5d",T->Data); //(访问)打印结点
T=T->Right; //转向右子树
}
}
}
先序遍历非递归遍历算法
void InOrderTraversal(BinTree BT)
{
BinTree T=BT;
Stack S=CreatStack(MaxSize); //创建并初始化堆栈S
while( T || !IsEmpty(S)){
while(T){ //一直向左并将沿途结点压入堆栈
push(S,T);
printf("%5d",T->Data); //(访问)打印结点。第一次碰到结点便输出
T=T->Left;
}
if(!IsEmpty(S)){
T=Pop(S); //结点弹出堆栈
T=T->Right; //转向右子树
}
}
}
5.层序遍历
二叉树遍历的核心问题:二叉树结构的线性化
二叉树遍历的本质是怎么将一个二维结构变成一维的线性序列的过程。对于一个同样的二维结构,用不同的遍历方法就会产生不同的一维序列。
- 从结点访问其左、右儿子结点
- 访问左儿子后,右儿子结点怎么办?
需要一个存储结构保存暂时不访问的结点
存储结构:堆栈、队列
当我们用堆栈保存的时候,访问结点并保存结点,然后访问左儿子,之后返回来时访问右儿子
当我们用队列保存的时候,从结点出发,访问了左右两个儿子,先往左边走,把右儿子保存到队列里面去。下面具体说怎么用队列保存我们准备访问的结点。
队列实现:遍历从根结点开始,首先将根结点入队,然后开始执行循环;结点出队,访问该结点,其左右儿子入队
首先从根结点开始,把A放入队列中。
A 层序遍历=>
然后将根结点抛出来,将其左右儿子放入队列。
B C 层序遍历=> A
A就是我们遍历的一个结果
从队列里抛出第一个元素B,将其左右儿子放入队列。
C D F 层序遍历=> A B
从队列里抛出第一个元素C,将其左右儿子放入队列。
D F G I 层序遍历=> A B C
从队列里抛出第一个元素D
F G I 层序遍历=> A B C D
依次进行下去。
最后层序遍历=> A B C D F G I E H
这个序列的特征:一层一层访问的。
层序基本过程:先根结点入队,然后:
- 从队列中取出一个元素;
- 访问该元素所指结点;
- 若该元素所指结点的左、右孩子结点非空,则将其左、右孩子的指针顺序入队。
void LevelOrderTraversal(BinTree BT)
{
Queue Q;
BinTree T;
if(!BT) return; //若是空树则直接返回
Q = CreatQueue( MaxSize); //创建并初始化队列Q
AddQ(Q,BT); //把根结点放到队列里面
while( !IsEmptyQ(Q)){
T=DeleteQ(Q);
printf("%d\n",T->Data); //访问取出队列的结点
if(T->Left) AddQ(Q,T->Left);
if(T->Right) AddQ(Q,T->Right);
}
}