第六章 树
树是n个结点的有限集,n=0时,称为空树,在任意一颗非空树中:
- 有且仅有一个特定的称为根的结点(Root)
- 当n>1时,其余结点可分为m个互不相交的有限集合(T1,T2,…,Tn),其中每个集合本身又是一棵树,并且车管我根的子树(Subtree)
6.1 树的定义
树的结点包含一个数据元素及若干个指向其子树的分支,结点拥有的子树数量称为结点的度。
结点的分类:
- 叶结点/终端结点:度为0的结点
- 分支结点/非终端结点:度不为0的结点
树的度:树内各个结点的度的最大值
树的路径长度:每个结点路径长度之和
上图的树的度为3
结点间的关系:
结点的子树的根称为该结点的孩子,该结点称为孩子的双亲,同一个双亲的孩子称为兄弟,结点的祖先是从根节点到该结点所经分支上的所有结点。
结点度之和=结点数-1
结点的层次:
层次(Level)从根开始定义,树中结点的最大层次称为树的深度(Depth)或高度。
有序树:树中结点的各个子树看成是从左至右有次序的,不能互换
无序树:可以互换
6.2 二叉树的定义
二叉树(Binary tree)是n个结点的有限集合,该集合或者为空集(空二叉树),或者由一个根节点和两颗互不相交、分别称为根节点的左子树和右子树的二叉树组成。
6.2.1 特殊二叉树
1. 满二叉树
所有分支节点都存在左子树和右子树,并且所有叶子都在同一层上的二叉树。
2.完全二叉树
对一棵有n个结点的二叉树按层序编号,如果编号为i的结点与同样深度的满二叉树中编号为i的节点在二叉树中的位置完全相同,则成全完全二叉树。
6.2.2 二叉树的性质
-
在二叉树的第i层,至多有2i−1个结点
-
深度为k的二叉树最多有2k−1个结点
-
对任何一棵二叉树T,如果其终端结点数为n0,度为2的结点数为n2,则n0=n2+1
-
具有n个结点的完全二叉树的深度为**[log2n]+1**([x]表示不大于x的最大整数)
-
对任意二叉树n1为奇数,完全二叉树n1=0/1,哈夫曼树n1=0
6.2.3 二叉树的存储结构
1、二叉树的顺序存储结构
用一维数组存储二叉树中的结点,并且结点的存储位置,也就是数组的下标要能体现结点之间的逻辑关系。
完全二叉树:
一般二叉树:
可以将其按完全二叉树来编号,不过把不存在的结点设置为空而已:
但是这样一来会对存储空间浪费,所以顺序存储结构一般只用于完全二叉树
2、二叉链表
data->数据域,lchile和rchile->左孩子和右孩子的指针
6.3 二叉树的遍历
1、前序遍历
若二叉树为空,则空操作返回,否则先访问根节点,然后前序遍历左子树,再前序遍历右子树,下图遍历顺序为:ABDGHCEIF
void PreOrder(BiTree T){
if(T !=NULL){
visit(T);
PreOrder(T->lchild);
PreOrder(T->rchild);
}
}
void PreOrder2(BiTree T){
InitStack(S);
BiTree p=T;
while(p){
if(p){
visit(p);
Push(S,p);
p=p->lchild;
}else{
Pop(S,p);
p=p->rchild;
}
}
}
2、中序遍历
若二叉树为空,则空操作返回,否则从根节点开始(并不访问),中序遍历左子树,然后访问根节点,最后中序遍历右子树,下图遍历顺序为:GDHBAEICF
void InOrder(BiTree T){
if(T !=NULL){
PreOrder(T->lchild);
visit(T);
PreOrder(T->rchild);
}
}
void InOrder2(BiTree T){
InitStack(S);
BiTree p=T;
while(p){
if(p){
Push(S,p);
p=p->lchild;
}else{
Pop(S,p);
visit(p);
p=p->rchild;
}
}
}
3、后序遍历
若二叉树为空,则空操作返回,否则从左到右,先叶子后结点的方式遍历访问左右子树,最后访问根节点,,下图遍历顺序为:GHDBIEFCA,可以找到子节点到父节点的路径
void PostOrder(BiTree T){
if(T !=NULL){
PreOrder(T->lchild);
PreOrder(T->rchild);
visit(T);
}
}
void PostOrder2(BiTree T){
InitStack(S);
BiTree q=T;
BiTree r=NULL;
while(q && IsEmpty(s)){ //结点和栈不空,则继续循环
if(p){
Push(S,q);
p=p->lchild;
}else{
GetTop(S,q);
if(q->rchild && q-rchild!=r) //右子树不空且未被访问过,则转向右子树
q=q->rchild;
else{ //若右子树已被访问,则弹出结点
Pop(S,q);
visit(q);
r=q;
q=NULL;
}
}
}
}
4、层序遍历
需要队列支持,若二叉树为空,则空操作返回,否则从树的第一层,也就是根结点开始访问,从上而下逐层遍历,同一层中,按从左到右的顺序对结点逐个访问,遍历顺序为:ABCDEFGHI
void LevelOrder (BiTree T){
InitQueue(Q);
BiTree p;
EnQueue(Q,T);
while(!IsEmpty(Q)){ //队列不空则循环
DeQueue(Q,p);
visit(p); //访问出队结点
if(p->lchild!=Null)
EnQueue(Q,p->lchild); //如果左子树不空,则左子树结点入队
if(p->rchild!=NULL)
EnQueue(Q,p->rchild); //如果右子树不空,则右子树结点入队
}
}
5. 其他常用算法
1. 求树高
int BtDepth(BiTree T){
if(T == NULL)
return 0;
ldep=BtDepth(T->lchild);
rdep=BtDepth(T->rchild);
if(ldep>rdep)
return ldep+1;
else
return rdep+1;
}
6.4 线索二叉树
常考空指针域的情况和线索二叉树的缺陷
6.4 树、森林、二叉树的转换
6.4.1 树转换为二叉树
6.4.2 森林转换为二叉树
6.4.3 二叉树转换为树
6.4.4 二叉树转换为森林
6.5 树与森林的遍历
总结:树、森林的先根遍历对应二叉树的先序遍历,后根遍历对应二叉树的后序遍历
6.6 平衡二叉树
- n层平衡二叉树只少要m个结点(当非叶子结点的平衡因子均为1或-1时成立):n1=1,n2=2,n3=4,n4=7,n5=12,n6=20…
- 平衡因子:左子树与右子树的高度差
- 最后插入的叶子节点不一定还是叶子结点
- 若删除叶子结点再插入,平衡二叉树可能相同可能不同
- 若删除非叶子结点再插入,平衡二叉树可能相同可能不同
6.7 哈夫曼树
结点的带权路径:该结点到树根之间的路径长度与结点上权的乘积
树的带权路径:树中所有结点的带权路径长度之和
哈夫曼树:带权路径长度WPL最小的二叉树,称为哈夫曼树,也叫最优二叉树
1.哈夫曼编码
结点的带权路径:该结点到树根之间的路径长度与结点上权的乘积
树的带权路径:树中所有结点的带权路径长度之和
哈夫曼树:带权路径长度WPL最小的二叉树,称为哈夫曼树,也叫最优二叉树