济南大学数据结构第六章Word格式.docx
《济南大学数据结构第六章Word格式.docx》由会员分享,可在线阅读,更多相关《济南大学数据结构第六章Word格式.docx(52页珍藏版)》请在冰豆网上搜索。
6.2.1二叉树的定义
二叉树是n(n≥0)个结点的有限集,它或者是空集,或者是由一个根和称为左、右子树的两个互不相交的二叉树组成。
二叉树是一个递归定义。
根据定义,二叉树通常具有5种基本形态:
6.1节关于树的基本术语也都适用于二叉树。
树的子树次序不作规定,
树中结点的度没有限制,
二叉树的两个子树有左、右之分。
二叉树中结点的度只能取0、1、2。
抽象数据类型—二叉树的定义:
ADTBinaryTree
数据对象D:
D是具有相同结构的数据元素的集合。
数据关系R:
D=Φ,则为空二叉树。
D≠Φ,则D=(root,DL,DR)。
root:
根结点
DL:
root的左子树
DR:
root的右子树
基本操作P:
6.2.2二叉树的性质
性质1:
在二叉树的第i层上至多有2i-1个结点(i≥1)。
归纳法证明:
(1)i=1,只有一个根结点,2i-1=20=1,正确;
(2)假设i-1成立,即第i-1层上至多有2i-2个结点;
(3)由于二叉树的结点的度至多为2,故在第i层上的最大结点数为第i-1层上的最大结点数的2倍,即2×
2i-2=2i-1。
性质2:
深度为k的二叉树至多有2k–1个结点(k≥1)。
作业:
归纳法证明。
引论:
一棵树有n个结点,则必有n–1条分支。
证明:
除根结点外,其它结点都有一个分支进入,
设B为分支总数,
则B=n-1
性质3:
对任何一棵二叉树T,如果其终端结点数为n0,度为2的结点数为n2,则n0=n2+1。
(1)已知,终端结点数为n0,度为2的结点数为n2,
设度为1的结点数为n1,
由于二叉树中的所有结点的度只能为0、1、2,
故二叉树的结点总数为n=n0+n1+n2;
(2)除根结点外,其它结点都有一个分支进入,
设B为分支总数,故n=B+1,
由于这些分支均是由度为1或2的结点发出的,
所以有B=n1+2n2,故n=n1+2n2+1,
由
(1)和
(2),可得n0+n1+n2=n1+2n2+1,
故有n0=n2+1。
两种特殊形态二叉树:
满二叉树、完全二叉树。
一棵深度为k且有2k-1个结点的二叉树称为满二叉树。
特点:
(1)每一层的结点数都达到最大结点数。
(2)叶子(终端)结点一定在最大层。
(3)任一结点,其左、右分支下的子孙的最大层次相等。
对满二叉树的结点进行连续编号,从根结点起,自上而下,自左至右,1、2、3、……、2k-1。
深度为k的,有n≤k个结点的二叉树,当且仅当其每一个结点都与深度为k的满二叉树中编号从1至n的结点一一对应时,称为完全二叉树。
(1)除最下一层,每一层的结点数都达到最大结点数。
(2)叶子结点只可能在层次最大的两层上出现。
(3)对任一结点,若其右分支的子孙的最大层次为l,则其左分支下的子孙的最大层次必为l或l+1。
性质4:
具有n个结点的完全二叉树的深度为log2n」+1。
设n结点完全二叉树的深度为k,
已知,
k-1层满二叉树结点数<k层完全二叉树结点数≤k层满二叉树结点数
故2k-1-1<n≤2k-1
有2k-1≤n<2k
又k-1≤log2n<k
因为k是整数,所以k=log2n」+1。
性质5:
如果对一棵有n个结点的完全二叉树的结点按层序编号(从第1层到第⎣log2n⎦+1层,每层从左到右),则对任一结点i(1≤i≤n),有
(1)如果i=1,则结点i为根,无父亲;
如果i>1,则结点i的父结点是结点⎣i/2⎦。
//求父亲
(2)如果2i≤n,则结点i的左儿子是结点2i;
否则无左儿子。
//求左儿子
(3)如果2i+1≤n,则结点i的右儿子是结点2i+1;
否则无右儿子。
//求右儿子
证明
(1):
如果
(2)、(3)成立,则
(1)成立。
证明
(2)和(3):
i=1:
若有左儿子,应为2i=2;
若有右儿子,应为2i+1=3;
设对结点i成立,即结点i的左儿子为结点2i,右儿子为结点2i+1;
求证对结点i+1也成立:
完全二叉树中,结点i和结点i+1之间的关系通常有两种情况,或在同一层上,或分别在相邻两层上,且最左和最右。
结点i+1的儿子的序号一定紧挨在结点i的儿子的序号的后面,
根据归纳假设,结点i的儿子的序号为2i和2i+1,
则结点i+1的左、右儿子的序号应为2i+2=2(i+1)、2i+3=2(i+1)+1。
6.2.3二叉树的存储结构
链式存储结构
链表结点包含3个域:
数据域、左指针域、右指针域
由这种结点结构构成的二叉树存储结构称为二叉链表。
二叉链表存储表示
typedefstructBiTNode{
TElemTypedata;
structBiTNode*lchild,*rchild;
}BiTNode,*BiTree;
为了方面某些操作有时需在结点中增加一个指向父亲的指针。
基本操作P:
InitBiTree(&
T)
DestroyBiTree(&
ClearBiTree(&
BiTreeIsEmpty(T)
BiTreeDepth(T)
Root(T,&
e)
Value(T,e,&
value)
Assign(T,&
e,value)
Parent(T,e)
LeftChild(T,e)
RightChild(T,e)
LeftSibling(T,e)
RightSibling(T,e)
InsertChild(T,e,x)
H
∧
A
B
C
D
E
F
G
操作:
二叉树T存在,向T中插入一个信息域为x的结点,作为T中信息域为e的结点的左儿子,而其原来的左子树为新结点的左子树。
DeleteChild(T,e)
删除T中信息域为e的结点为根的子树。
PreOrderTraverse(T,visit())
先序遍历二叉树T。
InOrderTraverse(T,visit())
中序遍历二叉树T。
PostOrderTraverse(T,visit())
后序遍历二叉树T。
LevelOrderTraverse(T,visit())
层次遍历二叉树T。
6.3基本操作
6.3.1遍历二叉树
遍历二叉树:
按某条搜索路径巡访二叉树中的每一个结点,使得每个结点均被访问一次,且仅一次。
非线性结构—二叉树
D=(root,DL,DR)。
如果能依次遍历这三部分,就可以遍历整个二叉树;
设以D、L、R分别表示访问根结点、遍历左子树、遍历右子树;
则可以存在6种遍历方案:
DLR、DRL、LDR、RDL、LRD、RLD;
若限定先左后右的原则,则只有3种情况:
先(根)序DLR遍历、中(根)序LDR遍历、后(根)序LRD遍历。
先(根)序遍历:
若二叉树为空,则返回;
否则
(1)访问根结点,打印;
(2)先(根)序遍历左子树;
(3)先(根)序遍历右子树;
算法6.1先序遍历递归算法
}*BiTree;
StatusPreOrderTraverse(BiTreeT){
if(T==NULL)returnOK;
printf(T->
data);
PreOrderTraverse(T->
lchild);
rchild);
}
先序遍历
先序遍历顺序:
ABDFGCEH
中(根)序遍历:
(1)中(根)序遍历左子树;
(2)访问根结点;
(3)中(根)序遍历右子树;
算法6.2中序遍历递归算法
StatusInOrderTraverse(BiTreeT){
InOrderTraverse(T->
中序遍历
中序遍历顺序:
BFDGACEH
后(根)序遍历:
(1)后(根)序遍历左子树;
(2)后(根)序遍历右子树;
(3)访问根结点;
算法6.3后序遍历递归算法
StatusPostOrderTraverse(BiTreeT){
后序遍历
后序遍历顺序:
FGDBHECA
作业:
写出二叉树的先、中、后序遍历顺序
思考作业:
1、已知二叉树先序遍历为ABDFIGCEHJ,中序遍历为BIFDGAEJHC,试构造二叉树。
2、已知二叉树两种遍历顺序,是否可以构造二叉树。
层次遍历
层次遍历是对二叉树从上到下、从左到右按层次进行遍历。
层次遍历:
HC,DF,EA
队列实现层次遍历
6.3.2二叉树插入操作
InsertChild(T,A,X)
二叉树T存在,向T中插入一个信息域为X的结点,作为T中信息域为A的结点的左儿子,而其原来的左子树为新结点的左子树。
思想:
1.首先找到信息域为A的结点p。
2.建立信息域为X的新结点q。
3.q->
lchild=p->
lchild。
4.p->
lchild=q。
6.3.3线索二叉树
二叉树的遍历实现了对一个非线性结构进行线性化的操作,从而使每个结点在线性序列中有且仅有一个直接前驱和直接后继。
但二叉链表中,如何能够不通过遍历直接找到任意结点的前驱和后继结点?
方法1:
增加两个指针域fwd和bkwd,分别指示其前驱和后继。
优点:
实现方便、简单。
缺点:
需要大量额外空间。
方法2:
利用闲余的空链域。
性质:
含有n个结点的二叉链表中有n+1个空链域。
(1)
设,终端结点数为n0,
度为1的结点数为n1,
度为2的结点数为n2,
(2)
空链域个数为K,
K=2n0+n1,
已知,n0=n2+1,
K=n0+n1+n2+1
=n+1
如何利用空链域描述前驱和后继信息?
其中:
LTag=0lchild域指示结点的左儿子
1lchild域指示结点的前驱结点
RTag=
0rchild域指示结点的右儿子
1rchild域指示结点的后继结点
增加线索的二叉树称之为线索二叉树。
二叉线索树的存储表示
typedefstructBiThrNode{
intdata;
structBiThrNode*lchild;
structBiThrNode*rchild;
unsignedLTag:
1;
unsignedRTag:
}*BiThrTree;
线索二叉链表的建立是依据二叉树的遍历顺序的。
例,中序线索链表
如何寻找结点的后继结点?
方法:
1.找到其右子树的根结点
2.顺其左指针往下直至其左标志为1的结点。
如何寻找结点的前驱结点?
1.找到其左子树的根结点
2.顺其右指针往下直至其右标志为1的结点。
中序线索二叉树寻找某个结点的前驱。
BiThrTreePrior(BiThrTreep)
思想:
如果Ltag=1直接返回前驱结点(p->
lchild);
否则
先取左儿子(p->
然后沿右儿子指针循环查找,直到Rtag=1为止。
利用中序线索二叉树寻找前驱实现二叉树的逆序遍历。
StatusInorderReverseTraverse_Thr(BiThrTreeHead){
寻找逆序的第一个结点p(头结点的后继结点);
while(p!
=Head){
printf(p->
data);
p=Prior(p);
注意:
寻找结点的前驱、后继的方法是依赖于遍历顺序的。
后序线索树寻找后继结点就很复杂。
1.若RTag=1,rchild直接指向后继。
2.若结点为右儿子,或为左儿子且其父亲没有右子树,则其后继为其父亲。
3.若结点为左儿子且其父亲有右子树,则其后继为其父亲的右子树按后序遍历所得的第一个结点。
ABCDEFGHI
可见,在后序线索树上寻找后继结点需要知道父亲结点。
故通常采用带标志域的三叉链表做存储结构。
6.4树和森林
6.4.1树的存储结构
父亲表示法
儿子表示法
链式存储
顺序存储
父亲儿子表示法
二叉树表示法
一.父亲表示法
用一组地址连续空间存储树的结点,每个结点由两个域组成。
树的父亲表示法存储表示
typedefstructPTNode{
chardata;
//数据域
intfather;
//父结点指示器
}PTNode;
//定义树结点
#defineMAX_TREE_SIZE100//定义最大结点数
PTNodetree[MAX_TREE_SIZE];
//顺序结构存储
结构特点:
获取祖先结点(直至根结点)比较方便
获取某个结点的儿子结点需要遍历整个结构
二.儿子表示法
1.多叉树表示法——链式存储
链表中的结点存在两种格式:
每个结点均采用定长格式,n为树的度。
childdegree
child2
child1
data
...
degree
每个结点采用变长格式,degree为该结点的度。
结构分析
定长操作方便,变长操作不方便
定长浪费空间,变长节省空间
采用定长存储格式,链表中存在很多空链域,造成空间浪费。
定义:
在一棵有n个结点,度为k的树中必有(k-1)n+1个空链域。
(1)设度为0、1、、k的结点数分别为n0、n1、、nk
首先n=n0+n1++nk
(2)设树中空链域总数为X
则有X=kn0+(k-1)n1++1nk-1+0nk
=kn0+(k-1)n1++(k-(k-1))nk-1+(k-k)nk
=kn0+kn1++knk-1+knk
-(n1+2n2++(k-1)nk-1+knk)
=kn-(n1+2n2++(k-1)nk-1+knk)
转而证明n1+2n2++(k-1)nk-1+knk=n-1
除根结点外,其它结点都有一个分支进入,
设B为分支总数,故B=n-1,
又由于这些分支均是由度为1、2、、k的结点引出的,
所以有B=n1+2n2++(k-1)nk-1+knk
得证
2.顺序存储
把每个结点的儿子结点以单链表的结构存储;
则n个结点就构成了n条儿子单链表;
将n条单链表头指针以顺序线性表存储;
方便搜索儿子结点
查找父结点需要从头遍历整个顺序表
如何既能方便搜索儿子结点,又能方便查找父结点?
三.父亲儿子表示法
1
2
3
4
5
6
7
8
9
B∧
A
C
D∧
E∧
R
F
G∧
H∧
K∧
-1
四.二叉树表示法
将树以二叉树的形式表示。
令二叉树结点的两个链域分别指向该结点的第一个儿子和右边的亲兄弟。
查找结点的兄弟:
沿结点的nextsibling域可找到所有亲兄弟。
查找结点的儿子:
沿结点的firstchild域可找到第一个儿子
然后再沿nextsibling域可找到其它儿子
查找结点的父亲:
为每个结点增加一个father域指向父结点。
typedefstructCSNode{
structCSNode*firstchild;
structCSNode*nextsibling;
}CSNode;
//结点
6.4.2森林与二叉树的转换
(1).任何一棵树都可以转换为一棵没有左子树的二叉树。
(2).森林是由若干棵树构成的集合,若把森林中每棵树的根结点看成是亲兄弟,就可以导出森林与二叉树的转换。
1.森林转换成二叉树
(1)增加一个根结点,作为原森林中各树根结点的父结点。
(2)将新树转换成二叉树。
(3)删除二叉树的根结点。
I
J
R
2.二叉树转换成森林
(1)增加一个新根结点,原二叉树根结点做为新根结点的左儿子。
(2)将新二叉树转换成树。
(3)删除树的根结点变为森林。
6.4.3森林和树的遍历
1.分割
从结构上,可以把任意的森林分成三部分:
1)第一棵树的根结点。
2)第一棵树根结点的子树构成的森林。
3)其余的树构成的森林。
按根结点在这三部分中的遍历次序,可把森林的遍历次序分为前序、中序、后序。
森林的前序遍历
1)访问第一棵树的根结点。
2)按前序遍历根结点的子树构成的森林。
3)按前序遍历其余的树构成的森林。
森林的中序遍历
1)按中序遍历第一棵树根结点的子树构成的森林。
2)访问第一棵树的根结点。
3)按中序遍历其余的树构成的森林。
森林的后序遍历
1)按后序遍历第一棵树根结点的子树构成的森林。
2)按后序遍历其余的树构成的森林。
3)访问第一棵树的根结点。
启示:
森林的前、中、后序遍历与对应二叉树的前、中、后序遍历一致。
画出二叉树转换的森林,并先序、中序、后序遍历森林。
6.6树的应用
1.二叉分类树(二叉排序树)
2.赫夫曼树(最优二叉树)
二叉分类树或者是一棵空树;
或者是具有下列性质的二叉树:
(1)左子树上所有结点的值均小于等于它的根结点的值;
(2)右子树上所有结点的值均大于它的根结点的值;
(3)根结点的左、右子树也分别为二叉分类树。
利用插入操作可以构造一棵二叉分类树
首先给出结点序列:
13、8、23、5、18、37
2.赫夫曼树Huffman(最优二叉树)
2.1基本概念:
从树中一个结点到另一个结点之间的分支构成这两个结点之间的路径。
路径上的分支数目称做结点到结点的路径长度。
树的路径长度是从树根到每一个结点的路径长度之和。
树的路径长度为11
推广,为结点增加权值w。
结点的带权