二叉树
二叉树
二叉树(Binary Tree):一个有穷的结点集合
- 集合可以为空
- 若不为空,则它是由根结点以及它的左子树Tl和右子树Tr的两个不相交的二叉树组成
- 二叉树五种具体基本形态
- 空树/叶结点/只有左子树/只有右子树/同时具有左子树和右子树
- 二叉树的子树有左右顺序之分
二叉树类型(结点分布形态的角度)
- 斜二叉树(Skewed Binary Tree)
- 只在一个方向上有孩子结点,实际上相当于一个线性结构
- 完美二叉树(Perfect Binary Tree)
- 满二叉树(Full Binary Tree)
- 完全二叉树(Complete Binary Tree)
- 完全二叉树和满二叉树的关系:
- 有n个结点的二叉树,对树中结点按从上至下、从左到右顺序进行编号,编号为i(1<=i<=n)结点与满二叉树中编号为i结点在二叉树中位置相同(允许缺失最后几个结点)
- 完全二叉树和满二叉树的关系:
二叉树的几个重要性质
- 一个二叉树第i层的最大结点数为:
- 深度为k的二叉树有最大结点总数(满二叉树)为:
- 对任何非空二叉树T,若表示叶结点的个数,是度为2的非叶结点个数
- 则二者满足关系:
- 边总数 ——>
- 结点总数 ——>
- 则二者满足关系:
二叉树的抽象数据类型定义
类型名称:二叉树
数据对象集:一个有穷的结点集合
若不为空,则由根结点和其左、右二叉树组成
操作集:BT ∈ BinTree,Item ∈ ElementType,重要操作有:
Boolean IsEmpty(BinTree BT):判别BT是否为空;
void Traversal(BinTree BT):遍历,按某顺序访问各个结点;
BinTree CreatBinTree():创建一个二叉树;
对二叉树而言,很多算法是建立在遍历之上的(很多算法需要先将二叉树的结点元素整体先读一遍)
常用的遍历方法有:
void PreOrderTraversal(BinTree BT):先序————根、左子树、右子树;
void InOrderTraversal(BinTree BT):中序————左子树、根、右子树;
void PostOrderTraversal(BinTree BT):后序————左子树、右子树、根;
void LevelOrderTraversal(BinTree BT):层序遍历,从上到下、从左到右;
二叉树的存储结构
二叉树顺序存储实现
适合完全二叉树:
- 按从上至下、从左到右顺序存储(完全二叉树从左到右没有空结点)
- n个结点的完全二叉树的结点父子关系:
- 非根结点(序号i>1)的父结点的序号是⌊i/2⌋;
- 结点(序号为i)的左孩子结点的序号是2i,(若2i<=n,否则没有左孩子);
- 结点(序号为i)的右孩子结点的序号是2i+1,(若2i+1<=n,否则没有右孩子);
对一般二叉树也可以采用这种结构,但需要在其相对完全二叉树而言空缺的位置上设置为空,因此会造成较多的空间浪费
二叉树链式存储实现
typedef struct TreeNode *BinTree;
typedef BinTree Position;
struct TreeNode
{
ElementType Data;
BinTree Left;
BinTree Right;
};
二叉树的遍历
例:假设二叉树结构为根结点A 左子树(B(DF(E))) 右子树(C(G(H)I))
先序遍历
遍历过程为:
- 访问根结点
- 先序遍历其左子树
- 先序遍历其右子树
上例先序遍历结果=>A B D F E C G H I
先序遍历递归算法
void PreOrederTraversal(BinTree BT)
{
if(BT)
{
printf("%d",BT->Data);
PreOrderTraversal(BT->Left);
PreOrderTraversal(BT->Right);
}
}
先序遍历非递归算法
- 递归本质就是靠堆栈实现,因此非递归算法也可以借助堆栈
void PreOrderTraversal(BinTree BT)
{
BinTree T=BT; //创建一个用于遍历的二叉树指针
Stack S=CreateStack(MaxSize) //创建并初始化堆栈S
while(T||!IsEmpty(S))
{
while(T) //一直向左直到叶结点将沿途结点压入堆栈
{
Push(S,T);
cout<<T->Data<<endl; //(访问)打印结点
T=T->Left;
}
if(!IsEmpty(S))
{
T=Pop(S); //结点弹出堆栈(左子树先出)
T=T->Right; //然后转向右子树(一步)
}
}
}
中序遍历
遍历过程为:
- 中序遍历其左子树
- 访问根结点
- 中序遍历其右子树
上例中序遍历结果=>D B E F A G H C I
中序遍历递归算法
void InOrederTraversal(BinTree BT)
{
if(BT)
{
PreOrderTraversal(BT->Left);
printf("%d",BT->Data);
PreOrderTraversal(BT->Right);
}
}
中序遍历非递归算法(结构基本同先序的非递归遍历,只有访问时机不一样):
- 遇到一个结点,将其压栈,并遍历左子树
- 当左子树遍历结束后,从栈顶弹出这个结点并访问它
- 按其右指针再去中序遍历该结点的右子树
void InOrderTraversal(BinTree BT)
{
BinTree T=BT;
Stack S=CreateStack(MaxSize)
while(T||!IsEmpty(S))
{
while(T)
{
Push(S,T);
T=T->Left;
}
if(!IsEmpty(S))
{
T=Pop(S);
cout<<T->Data<<endl;
T=T->Right;
}
}
}
后序遍历
遍历过程为:
- 后序遍历其左子树
- 后序遍历其右子树
- 访问根结点
上例后序遍历结果=>D E F B H G I C A
后序递归遍历算法
void PostOrederTraversal(BinTree BT)
{
if(BT)
{
PostOrderTraversal(BT->Left);
PostOrderTraversal(BT->Right);
printf("%d",BT->Data);
}
}
后序非递归遍历算法(非递归写法与前序中序的思路并不完全一致)
- 算法1:采用双堆栈,使用一个堆栈记录另一个堆栈使用根右左的顺序时元素的入栈顺序,然后将该堆栈的元素逆序输出
void PostOrderTraversal(BinTree BT)
{
BinTree T=BT;
Stack S1,S2;
S1=S2=CreateStack(MaxSize)
while(T||!IsEmpty(S))
{
while(T)
{
Push(S1,T);
Push(S2,T);
T=T->Right;
}
if(!IsEmpty(S1))
{
T=Pop(S1);
T=T->Left;
}
}
while(!IsEmpty(S2))
{
T=Pop(S2);
cout<<T->Data<<endl;
}
}
- 算法2:单堆栈实现后序遍历
void PostOrderTraversal(BinTree BT)
{
BinTree T,pre;
Stack S;
T=pre=BT;
while(T||!IsEmpty(S1))
{
while(T)
{
Push(S,T);
T=T->Left;
}
if(!IsEmpty(S2))
{
T=Pop(S1);
if(!T->Right||pre=T->Right)
{
cout<<T->Data<<endl;
pre=T;
}
else
{
Push(S,T);
T=T->Right;
}
}
}
}
先序、中序和后序遍历过程:
遍历过程中经过结点的路线一样(都是先先向左走,走到头以后回到根,然后再向右走)只是访问(如打印)各结点的时机不同
层序遍历
二叉树遍历的核心问题:二维结构的线性化(二维的树转化为一维的线性访问序列)
- 从结点访问其左、右孩子结点
- 访问左孩子后,右孩子怎么办
- 链表存在的一大问题:后面的结点不知道前面结点的位置
- 需要一个存储结构保存暂时不访问的结点
- 存储结构:堆栈、队列
- 两种存储结构都可以,只不过堆栈的目的是保存自身,队列是保存右孩子结点
非递归算法:队列实现
- 遍历从根结点开始,首先将根结点入队,然后执行循环:结点出队列、访问该结点、左右孩子入队
- 遍历过程为:
- 从队列中取出一个元素,访问该元素所指结点
- 若该元素所指结点的左、右孩子结点非空,则将其左、右孩子的指针顺序入队
上例层序遍历结果=>A B C D F G I E H
void LevelOrderTraversal(BinTree BT)
{
Queue Q;
BinTree T=BT;
Q=CreateQueue(MaxSize);
AddQ(Q,T);
while(IsEmptyQ(Q))
{
T=DeleteQ(Q);
cout<<T->Data<<endl;
if(T->Left)AddQ(Q,T->Left);
if(T->Right)AddQ(Q,T->Right);
}
}
递归算法:求出二叉树高度,逐层遍历,以每层高度控制递归深度
void onelevelTraversal(BinTree BT,int height){
if(!BT||!height)return NULL;
else if(height==1){
cout<<BT->Data;
return;
}
onelevelTraversal(BT->Left,h-1);
onelevelTraversal(BT->Right,h-1);
}
void LevelOrderTraversal(BinTree BT){
int height=Getheight(BT);
for(int i=1;i<=height;i++)onelevelTraversal(BT,i);
}
层序遍历的另一种思路:由于二叉树的特点,设根结点的下标为i,则左孩子结点的下标2* i,右孩子结点的下标为2* i+1
使用前序中序遍历的方法,在遍历时将孩子结点用数组存储(适用于完全二叉树情况)
void LevelOrderTraversal(BinTree BT,int i,int* levelorder) //i的初值为1
{
levelorder[i]=BT->Data;
if(BT->Left)LevelOrderTraversal(BT->Left,i*2);
if(BT->Right)LevelOrderTraversal(BT->Right,i*2+1);
}
从宏观角度来看:
- 前序、中序、后序遍历属于DFS
- 层序遍历属于BFS
遍历二叉树的应用
输出二叉树中的叶子结点
- 在二叉树的遍历算法中增加检测结点的“左右子树是否都为空”
void PreOrederTraversal(BinTree BT)
{
if(BT)if(!BT->Left&&!BT->Right)
{
printf("%d",BT->Data);
PreOrderTraversal(BT->Left);
PreOrderTraversal(BT->Right);
}
}
求二叉树的高度
int Getheight(BinTree BT)
{
int HL=0,HR=0,MaxH;
if(BT)
{
HL=PostOrderTraversal(BT->Left);
HR=PostOrderTraversal(BT->Right);
MaxH=(HL>HR)?HL:HR; //注意这里只能是后序遍历
return MaxH+1;
}
return 0;
}
二元运算表达式树及其遍历
- 叶子结点是操作数,非叶结点是运算符
- 三种遍历可以得到三种不同的访问结果:
- 先序遍历得到前缀表达式,如:+ + a * b c * + * d e f g
- 中序遍历得到中缀表达式,如:a + b * c + d * e + f * g
- 中缀表达式会受到运算符优先级的影响,所以直接遍历的结果可能不符合本来的运算规则
- 后序遍历得到中缀表达式,如:a b c * + d e * f + g * +
已知三种遍历中任意两种遍历序列,能否唯一确定一棵二叉树
- 其中必须要有中序序列才行
- 如果没有中序序列,则对一组双亲和孩子结点来说无法确定是左孩子还是右孩子
- 例:先序和中序遍历序列确定一棵二叉树
- 根据先序遍历序列第一个结点确定根结点
- 根据根结点在中序遍历序列中分割出左右两个子序列
- 对左子树和右子树分别递归使用相同方法继续分解
- 类似的,后序和中序遍历序列也可以确定一棵二叉树
例:二叉树先序序列中序序列转后序序列,后序序列中序序列转先序序列
int preorder[]={'A','B','D','E','C','F','G'},
inorder[]={'D','B','E','A','F','C','G'},
postorder[]={'D','E','B','F','G','C','A'};
void post(int root,int start,int end){
if(start>end)return;
int i=start;
for(;inorder[i]!=preorder[root];i++);
post(root+1,start,i-1);
post(root+1+i-start,i+1,end);
cout<<(char)inorder[i]<<' ';
}
void pre(int root,int start,int end){
if(start>end)return;
int i=end;
for(;inorder[i]!=postorder[root];i--);
cout<<(char)inorder[i]<<' ';
pre(root-1-end+i,start,i-1);
pre(root-1,i+1,end);
}
例:中序和前序序列建树(中序和后序同理)
BinTree CreateBintree(BinTree BST,int pres,int pree,int ins,int ine){
if(pree-pres<0)return nullptr;
int pos;
BST=new struct TreeNode(tpreorder[pres]);
for(pos=ins;tinorder[pos]!=tpreorder[pres];pos++);
BST->left=CreateBintree(BST->left,pres+1,pres+pos-ins,ins,pos-1);
BST->right=CreateBintree(BST->right,pres+pos-ins+1,pree,pos+1,ine);
return BST;
}
二叉树遍历演示
/* 浙江大学mooc数据结构示例程序 */
#include<cstdio>
typedef int ELementType;
typedef struct BinNode* BinTree;
struct BinNode
{
ELementType Data;
BinTree Left,Right;
};
typedef struct Queue* List;
struct Queue
{
int Maxsize;
List rear,front;
};
struct Queue CreatQueue();
void AddQ(Queue,BinTree);
bool IsEmpty(Queue);
BinTree DeleteQ(Queue);
void InorderTraversal( BinTree BT )
{
if( BT ) {
InorderTraversal( BT->Left );
/* 此处假设对BT结点的访问就是打印数据 */
printf("%d ", BT->Data); /* 假设数据为整型 */
InorderTraversal( BT->Right );
}
}
void PreorderTraversal( BinTree BT )
{
if( BT ) {
printf("%d ", BT->Data );
PreorderTraversal( BT->Left );
PreorderTraversal( BT->Right );
}
}
void PostorderTraversal( BinTree BT )
{
if( BT ) {
PostorderTraversal( BT->Left );
PostorderTraversal( BT->Right );
printf("%d ", BT->Data);
}
}
void LevelorderTraversal ( BinTree BT )
{
Queue Q;
BinTree T;
if ( !BT ) return; /* 若是空树则直接返回 */
Q = CreatQueue(); /* 创建空队列Q */
AddQ( Q, BT );
while ( !IsEmpty(Q) ) {
T = DeleteQ( Q );
printf("%d ", T->Data); /* 访问取出队列的结点 */
if ( T->Left ) AddQ( Q, T->Left );
if ( T->Right ) AddQ( Q, T->Right );
}
}
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 hetumessi's Blog!
评论
ValineGitalk