《第7章zzh树形结构.ppt》由会员分享,可在线阅读,更多相关《第7章zzh树形结构.ppt(148页珍藏版)》请在三一办公上搜索。
1、第7章 树形结构,7.1 树的基本概念7.2 二叉树概念和性质7.3 二叉树存储结构7.4 二叉树的遍历7.5 二叉树的基本运算及其实现7.6 二叉树的构造7.7 线索二叉树7.8 哈夫曼树7.9树与二叉树的转换、森林与二叉树的转换7.9 并查集 本章小结,7.1 树的基本概念,7.1.1 树的定义,7.1.3 树的基本术语,7.1.2 树的表示,7.1.4 树的性质,7.1.5 树的基本运算,7.1.6 树的存储结构,7.1.1 树的定义 形式化定义:树:TK,R。K是包含n个结点的有穷集合(n0),关系R满足以下条件:(1)有且仅有一个结点k0K,它对于关系R来说没有前驱结点,结点k0称作
2、树的根。(2)除结点k0外,K中的每个结点对于关系R来说都有且仅有一个前驱结点。(3)K中每个结点对于关系R来说可以有多个后继结点。,递归定义:树是由n(n0)个结点组成的有限集合(记为T)。其中,如果n=0,它是一棵空树,这是树的特例;如果n0,这n个结点中存在(有仅存在)一个结点作为树的根结点,简称为根(root),其余结点可分为m(m0)个互不相交的有限集T1,T2,Tm,其中每一棵子集本身又是一棵符合本定义的树,称为根root的子树。,7.1.2 树的表示,(1)树形表示法。这是树的最基本的表示,使用一棵倒置的树表示树结构,非常直观和形象。下图就是采用这种表示法。,(2)文氏图表示法。
3、使用集合以及集合的包含关系描述树结构。下图就是树的文氏图表示法。,(3)凹入表示法。使用线段的伸缩描述树结构。下图是树的凹入表示法。,(4)括号表示法。将树的根结点写在括号的左边,除根结点之外的其余结点写在括号中并用逗号间隔来描述树结构。下图是树的括号表示法。,7.1.3 树的基本术语 1.结点的度与树的度:树中某个结点的子树的个数称为该结点的度。树中各结点的度的最大值称为树的度,通常将度为m的树称为m次树。2.分支结点与叶结点:度不为零的结点称为非终端结点,又叫分支结点。度为零的结点称为终端结点或叶结点。在分支结点中,每个结点的分支数就是该结点的度。如对于度为1的结点,其分支数为1,被称为单
4、分支结点;对于度为2的结点,其分支数为2,被称为双分支结点,其余类推。,3.路径与路径长度:对于任意两个结点ki和kj,若树中存在一个结点序列ki,ki1,ki2,kin,kj,使得序列中除ki外的任一结点都是其在序列中的前一个结点的后继,则称该结点序列为由ki到kj的一条路径,用路径所通过的结点序列(ki,ki1,ki2,kj)表示这条路径。路径的长度等于路径所通过的结点数目减1(即路径上分支数目)。可见,路径就是从ki出发“自上而下”到达kj所通过的树中结点序列。显然,从树的根结点到树中其余结点均存在一条路径。,4.孩子结点、双亲结点和兄弟结点:在一棵树中,每个结点的后继,被称作该结点的孩
5、子结点(或子女结点)。相应地,该结点被称作孩子结点的双亲结点(或父母结点)。具有同一双亲的孩子结点互为兄弟结点。进一步推广这些关系,可以把每个结点的所有子树中的结点称为该结点的子孙结点,从树根结点到达该结点的路径上经过的所有结点被称作该结点的祖先结点。,5.结点的层次和树的高度:树中的每个结点都处在一定的层次上。结点的层次从树根开始定义,根结点为第1层,它的孩子结点为第2层,以此类推,一个结点所在的层次为其双亲结点所在的层次加1。树中结点的最大层次称为树的高度(或树的深度)。树中结点的最大层次称为树的高度(或树的深度)。6.有序树和无序树:若树中各结点的子树是按照一定的次序从左向右安排的,且相
6、对次序是不能随意变换的,则称为有序树,否则称为无序树。,7.森林:n(n0)个互不相交的树的集合称为森林。森林的概念与树的概念十分相近,因为只要把树的根结点删去就成了森林。反之,只要给n棵独立的树加上一个结点,并把这n棵树作为该结点的子树,则森林就变成了树。,7.1.4 树的性质性质1 树中的结点数等于所有结点的度数加1。证明:根据树的定义,在一棵树中,除树根结点外,每个结点有且仅有一个前驱结点。也就是说,每个结点与指向它的一个分支一一对应,所以除树根之外的结点数等于所有结点的分支数(度数),从而可得树中的结点数等于所有结点的度数加1。,性质2 度为m的树中第i层上至多有mi-1个结点,这里应
7、有i1。,证明(采用数学归纳法)对于第一层,因为树中的第一层上只有一个结点,即整个树的根结点,而由i=1代入mi-1,得mi-1=m1-1=1,也同样得到只有一个结点,显然结论成立。假设对于第(i-1)层(i1)命题成立,即度为m的树中第(i-1)层上至多有mi-2个结点,则根据树的度的定义,度为m的树中每个结点至多有m个孩子结点,所以第i层上的结点数至多为第(i-1)层上结点数的m倍,即至多为mi-2m=mi-1个,这与命题相同,故命题成立。,性质3 高度为h的m次树至多有 个结点。证明:由树的性质2可知,第i层上最多结点数为mi-1(i=1,2,h),显然当高度为h的m次树(即度为m的树)
8、上每一层都达到最多结点数时,整个m次树具有最多结点数,因此有:整个树的最多结点数=每一层最多结点数之和=m0+m1+m2+mh-1=。,性质4 具有n个结点的m次树的最小高度为logm(n(m-1)+1)。证明:设具有n个结点的m次树的高度为h,若在该树中前h-1层都是满的,即每一层的结点数都等于mi-1个(1ih-1),第h层(即最后一层)的结点数可能满,也可能不满,则该树具有最小的高度。其高度h可计算如下:,根据树的性质3可得:n乘(m-1)后得:mh-1n(m-1)+1mh以m为底取对数后得:h-1logm(n(m-1)+1)h即logm(n(m-1)+1)hlogm(n(m-1)+1)
9、+1因h只能取整数,所以 h=logm(n(m-1)+1)结论得证。,例7.1 含n个结点的三次树的最小高度是多少?最大高度是多少?解:设含n个结点的(为完全三次树时高度最小)的三次树的最小高度为h,则有:1+3+9+3h-2n1+3+9+3h-1(3h-1-1)/2 n(3h-1)/2 3h-12n+13h 即:h=log3(2n+1)最大高度为n-2。,7.1.5 树的基本运算,树的运算主要分为三大类:第一类,寻找满足某种特定关系的结点,如寻找当前结点的双亲结点等;第二类,插入或删除某个结点,如在树的当前结点上插入一个新结点或删除当前结点的第i个孩子结点等;第三类,遍历树中每个结点,这里着
10、重介绍。,树的遍历运算是指按某种方式访问树中的每一个结点且每一个结点只被访问一次。树的遍历运算的算法主要有先根遍历和后根遍历两种。注意,下面的先根遍历和后根遍历算法都是递归的。1.先根遍历 先根遍历过程为:(1)访问根结点;(2)按照从左到右的次序先根遍历根结点的每一棵子树。,2.后根遍历 后根遍历过程为:(1)按照从左到右的次序后根遍历根结点的每一棵子树;(2)访问根结点。,7.1.6 树的存储结构,1.双亲存储结构 这种存储结构是一种顺序存储结构,用一组连续空间存储树的所有结点,同时在每个结点中附设一个伪指针指示其双亲结点的位置。,树的双亲存储结构示意图,2.孩子链存储结构 孩子链存储结构
11、可按树的度(即树中所有结点度的最大值)设计结点的孩子结点指针域个数。下图(a)的树对应的孩子链存储结构如图(b)所示。,树的孩子链存储结构示意图,3.孩子兄弟链存储结构 孩子兄弟链存储结构是为每个结点设计三个域:一个数据元素域,一个该结点的第一个孩子结点指针域,一个该结点的下一个兄弟结点指针域。下图(a)的树对应的孩子兄弟链存储结构如图(b)所示。,树的孩子兄弟链存储结构示意图,7.2 二叉树概念和性质,7.2.1 二叉树概念,7.2.2 二叉树性质,7.2.3 二叉树与树、森林之间的转换,7.2.1 二叉树概念 二叉树也称为二次树或二分树,它是有限的结点集合,这个集合或者是空,或者由一个根结
12、点和两棵互不相交的称为左子树和右子树的二叉树组成。二叉树的定义是一种递归定义。,二叉树有五种基本形态,如下图所示,任何复杂的二叉树都是这五种基本形态的复合。,从定义看到,二叉树是一种特殊的树,其表示法也与树的表示法一样,有树形表示法、文氏图表示法、凹入表示法和括号表示法等。,在一棵二叉树中,如果所有分支结点都有左孩子结点和右孩子结点,并且叶结点都集中在二叉树的最下一层,这样的二叉树称为满二叉树。下图所示就是一棵满二叉树。可以对满二叉树的结点进行连续编号,约定编号从树根为1开始,按照层数从小到大、同一层从左到右的次序进行。图中每个结点外边的数字为对该结点的编号。,满二叉树的特点:基本特点是每一层
13、上的结点数总是最大结点数。满二叉树的所有的支结点都有左、右子树。可对满二叉树的结点进行连续编号,若规定从根结点开始,按“自上而下、自左至右”的原则进行,若二叉树中最多只有最下面两层的结点的度数可以小于2,并且最下面一层的叶结点 都依次排列在该层最左边的位置上,则这样的二叉树称为完全二叉树。如下图所示为一棵完全二叉树。同样可以对完全二叉树中每个结点进行连续编号,编号的方法同满二叉树相同。图中每个结点外边的数字为对该结点的编号。,完全二叉树是满二叉树的一部分,而满二叉树是完全二叉树的特例。完全二叉树的特点:若完全二叉树的深度为k,则所有的叶子结点都出现在第k层或k-1层。对于任一结点,如果其右子树
14、的最大层次为l,则其左子树的最大层次为l或l+1。,7.2.2 二叉树性质性质1:在非空二叉树中,第i层上至多有2i-1个结点(i1)。(为满二叉树时,结点最多:i=1时,第i层有1个结点;i=1时,第i层偶数个结点)性质2:深度为k的二叉树至多有2k-1个结点(k1)(包含根节点,结点总数为奇数)性质3:对任何一棵二叉树,若其叶子结点数为n0,度为2的结点数为n2,则n0=n2+1。性质4:n个结点的完全二叉树深度为:2n+1。其中符号:x表示不大于x的最大整数。(上口大)x 表示不小于x的最小整数。(上口小)书上二叉树的性质,性质5:若对一棵有n个结点的完全二叉树(深度为2n+1)的结点按
15、层(从第1层到第2n+1层)序自左至右进行编号,则对于编号为i(1in)的结点:若i=1:则结点i是二叉树的根,无双亲结点;否则,若i1,则其双亲结点编号是 i/2。如果2in:则结点i为叶子结点,无左孩子;否则,其左孩子结点编号是2i。如果2i+1n:则结点i无右孩子;否则,其右孩子结点编号是2i+1。,7.3 二叉树存储结构,7.3.1 二叉树的顺序存储结构,7.3.2 二叉树的链式存储结构,7.3.1 二叉树的顺序存储结构 二叉树的顺序存储结构中结点的存放次序是:对该树中每个结点进行编号,其编号从小到大的顺序就是结点存放在连续存储单元的先后次序。若把二叉树存储到一维数组中,则该编号就是下
16、标值加1(注意,C/C+语言中数组的起始下标为0)。树中各结点的编号与等高度的完全二叉树中对应位置上结点的编号相同。,二叉树存储结构的类型定义:#define MAX_SIZE 100 typedef telemtype sqbitreeMAX_SIZE;用一组地址连续的存储单元依次“自上而下、自左至右”存储完全二叉树的数据元素。,顺序存储结构,图6-6 二叉树及其顺序存储形式,最坏的情况下,一个深度为k且只有k个结点的单支树(二叉树每个结点只有一个孩子)需要长度为2k-1的一维数组。,7.3.2 二叉树的链式存储结构 在二叉树的链接存储中,结点的结构如下:typedef struct nod
17、e ElemType data;struct node*lchild,*rchild;BTNode;其中,data表示值域,用于存储对应的数据元素,lchild和rchild分别表示左指针域和右指针域,用于分别存储左孩子结点和右孩子结点(即左、右子树的根结点)的存储位置。,二叉树及其链式存储结构,7.4 二叉树的遍历,7.4.1 二叉树遍历的概念,7.4.2 二叉树遍历递归算法,7.4.3 二叉树遍历非递归算法,7.4.1 二叉树遍历的概念 二叉树的遍历是指按照一定次序访问树中所有结点,并且每个结点仅被访问一次的过程。它是最基本的运算,是二叉树中所有其他运算的基础。,1.先序遍历(先中后相对于
18、根结点,也即前序遍历)先序遍历二叉树的过程是:(1)访问根结点;(2)先序遍历左子树;(3)先序遍历右子树。2.中序遍历中序遍历二叉树的过程是:(1)中序遍历左子树;(2)访问根结点;(3)中序遍历右子树。,3.后序遍历后序遍历二叉树的过程是:(1)后序遍历左子树;(2)后序遍历右子树;(3)访问根结点。,(1)先序遍历:root-leftchild-rightchild,6.3 遍历二叉树,先序遍历序列:A B D C,(2)中序遍历:leftchild-root-rightchild,中序遍历序列:B D A C,6.3 遍历二叉树,(3)后序遍历:leftchild-rightchild
19、-root,后序遍历序列:D B C A,6.3 遍历二叉树,(4)层次遍历,后序层次遍历序列:A B C D,6.3 遍历二叉树,如图所示的二叉树表示表达式:(a+b*(c-d)-e/f)按不同的次序遍历此二叉树,将访问的结点按先后次序排列起来的次序是:其先序序列为:-+a*b-cd/ef 其中序序列为:a+b*c-d-e/f 其后序序列为:abcd-*+ef/-,(5)三种遍历的相互转换:已知先序遍历与后序遍历,求中序遍历;(不唯一)已知先序遍历与中序遍历,求后序遍历;(唯一)已知中序遍历和后序遍历,求先序遍历。(唯一),6.3 遍历二叉树,例1:中序遍历序列为:badcfeg后序遍历序列
20、为:bdfgeca先序遍历序列为?abcdefg,6.3 遍历二叉树,例2:先序遍历为:12345678;中序遍历为:32416587;后序遍历为?34268751,6.3 遍历二叉树,例3:先序遍历为:ABCDEGF;后序遍历为:CGEFDBA;中序遍历为?CBEGDFA,6.3 遍历二叉树,对于二叉树的遍历,分别讨论递归遍历算法和非递归遍历算法。递归遍历算法具有非常清晰的结构,但初学者往往难以接受或怀疑,不敢使用。实际上,递归算法是由系统通过使用堆栈来实现控制的。而非递归算法中的控制是由设计者定义和使用堆栈来实现的。,7.4.2 二叉树遍历递归算法1.先序遍历的递归算法算法的递归定义是:若
21、二叉树为空,则遍历结束;否则 访问根结点;先序遍历左子树(递归调用本算法);先序遍历右子树(递归调用本算法)。,void PreOrder(BTNode*b)/*先序遍历的递归算法*/if(b!=NULL)printf(%c,b-data);/*访问根结点*/PreOrder(b-lchild);PreOrder(b-rchild);,2.中序遍历的递归算法算法的递归定义是:若二叉树为空,则遍历结束;否则 中序遍历左子树(递归调用本算法);访问根结点;中序遍历右子树(递归调用本算法)。,void InOrder(BTNode*b)/*中序遍历的递归算法*/if(b!=NULL)InOrder(
22、b-lchild);printf(%c,b-data);/*访问根结点*/InOrder(b-rchild);,3.后序遍历的递归算法算法的递归定义是:若二叉树为空,则遍历结束;否则 后序遍历左子树(递归调用本算法);后序遍历右子树(递归调用本算法);访问根结点。,void PostOrder(BTNode*b)/*后序遍历递归算法*/if(b!=NULL)PostOrder(b-lchild);PostOrder(b-rchild);printf(%c,b-data);/*访问根结点*/,7.4.3 二叉树遍历非递归算法,1.先序遍历非递归算法设T是指向二叉树根结点的指针变量,非递归算法是:
23、若二叉树为空,则返回;否则,令p=T;访问p所指向的结点;q=p-Rchild,若q不为空,则q进栈;p=p-Lchild,若p不为空,转(1),否则转(4);退栈到p,转(1),直到栈空为止。,void PreOrder2(BTNode*b)BTNode*StMaxSize,*p;int top=-1;top+;Sttop=b;/根结点入栈while(top-1)/栈不为空时循环 p=Sttop;top-;/退栈并访问该结点 printf(%c,p-data);if(p-rchild!=NULL)/右孩子结点入栈 top+;Sttop=p-rchild;if(p-lchild!=NULL)/
24、左孩子结点入栈 top+;Sttop=p-lchild;,2.中序遍历非递归算法(2)第二种方法(常规方法)由中序遍历过程可知,采用一个栈保存需要返回的结点指针,先扫描(并非访问)根结点的所有左结点并将它们一一进栈。然后出栈一个结点,显然该结点没有左孩子结点或者左孩子结点已访问过(进一步说明该结点的左子树均已访问),则访问它。然后扫描该结点的右孩子结点,将其进栈,再扫描该右孩子结点的所有左结点并一一进栈,如此这样,直到栈空为止。,设T是指向二叉树根结点的指针变量,非递归算法是:若二叉树为空,则返回;否则,令p=T 若p不为空,p进栈,p=p-Lchild;否则(即p为空),退栈到p,访问p所指
25、向的结点;p=p-Rchild,转(1);直到栈空为止。,void InOrder2(BTNode*b)BTNode*StMaxSize,*p;int top=-1;p=b;while(top-1|p!=NULL)while(p!=NULL)/扫描*p的所有左结点并进栈 top+;Sttop=p;p=p-lchild;if(top-1)p=Sttop;top-;/出栈*p结点 printf(%c,p-data);/访问之 p=p-rchild;/扫描*p的右孩子结点/end of while(top-1|p!=NULL),找*b的最左下结点,3.后序遍历非递归算法在后序遍历中,根结点是最后被访
26、问的。因此,在遍历过程中,当搜索指针指向某一根结点时,不能立即访问,而要先遍历其左子树,此时根结点进栈。当其左子树遍历完后再搜索到该根结点时,还是不能访问,还需遍历其右子树。所以,此根结点还需再次进栈,当其右子树遍历完后再退栈到到该根结点时,才能被访问。因此,设立一个状态标志变量tag:,void PostOrder2(BTNode*b)BTNode*StMaxSize;BTNode*p;p=b;int flag,top=-1;/栈指针置初值do while(b!=NULL)/将*b的所有左结点进栈 top+;Sttop=b;b=b-lchild;p=NULL;/p指向栈顶结点的前一个已访问的
27、结点 flag=1;/设置b的访问标记为已访问过,找最左下结点,while(top!=-1,b的右孩子不存在或已访问过,从上述过程可知,栈中保存的是当前结点*b的所有祖先结点(均未访问过)。例如,求一个结点的所有祖先结点。,4.层次遍历其过程是:若二叉树非空(假设其高度为h),则:(1)访问根结点(第1层);(2)从左到右访问第2层的所有结点;(3)从左到右访问第3层的所有结点、第h层的所有结点。,层次遍历序列:A、B、C、D、E、F、G,void LevelOrder(BTNode*b)BTNode*p;BTNode*quMaxSize;/*定义环形队列,存放结点指针*/int front,
28、rear;/*定义队头和队尾指针*/front=rear=-1;/*置队列为空队列*/rear+;qurear=b;/*根结点指针进入队列*/while(front!=rear)/*队列不为空*/front=(front+1)%MaxSize;p=qufront;/*队头出队列*/printf(%c,p-data);/*访问结点*/,if(p-lchild!=NULL)/*有左孩子时将其进队*/rear=(rear+1)%MaxSize;qurear=p-lchild;if(p-rchild!=NULL)/*有右孩子时将其进队*/rear=(rear+1)%MaxSize;qurear=p-r
29、child;,例7.2 假设二叉树采用二叉链存储结构存储,试设计一个算法,输出一棵给定二叉树的所有叶子结点。解:输出一棵二叉树的所有叶子结点的递归模型f()如下:f(b):不做任何事件 若b=NULL f(b):输出*b结点的data域 若*b为叶子结点 f(b):f(b-lchild);f(b-rchild)其他情况,void DispLeaf(BTNode*b)if(b!=NULL)if(b-lchild=NULL,7.5 二叉树的基本运算及其实现,7.5.1 二叉树的基本运算概述,7.5.2 二叉树的基本运算算法实现,7.5.1 二叉树的基本运算概述 归纳起来,二叉树有以下基本运算:(1
30、)创建二叉树CreateBTNode(*b,*str):根据二叉树括号表示法的字符串*str生成对应的链式存储结构。(2)查找结点FindNode(*b,x):在二叉树b中寻找data域值为x的结点,并返回指向该结点的指针。(3)找孩子结点LchildNode(p)和RchildNode(p):分别求二叉树中结点*p的左孩子结点和右孩子结点。,(4)求高度BTNodeDepth(*b):求二叉树b的高度。若二叉树为空,则其高度为0;否则,其高度等于左子树与右子树中的最大高度加l。(5)输出二叉树DispBTNode(*b):以括号表示法输出一棵二叉树。,7.5.2 二叉树的基本运算算法实现(1
31、)创建二叉树CreateBTNode(*b,*str)用ch扫描采用括号表示法表示二叉树的字符串。分以下几种情况:若ch=(:则将前面刚创建的结点作为双亲结点进栈,并置k=1,表示其后创建的结点将作为这个结点的左孩子结点;若ch=):表示栈中结点的左右孩子结点处理完毕,退栈;若ch=,:表示其后创建的结点为右孩子结点;,其他情况,表示要创建一个结点,并根据k值建立它与栈中结点之间的联系,当k=1时,表示这个结点作为栈中结点的左孩子结点,当k=2时,表示这个结点作为栈中结点的右孩子结点。如此循环直到str处理完毕。算法中使用一个栈St保存双亲结点,top为其栈指针,k指定其后处理的结点是双亲结点
32、(保存在栈中)的左孩子结点(k=1)还是右孩子结点(k=2)。,void CreateBTNode(BTNode*/*为孩子结点右结点*/,default:p=(BTNode*)malloc(sizeof(BTNode);p-data=ch;p-lchild=p-rchild=NULL;if(b=NULL)/*p为二叉树的根结点*/b=p;else/*已建立二叉树根结点*/switch(k)case 1:Sttop-lchild=p;break;case 2:Sttop-rchild=p;break;j+;ch=strj;,例如,对于括号表示串A(B(D(,G),C(E,F),建立二叉树链式存
33、储结构的过程如下表所示。,生成的二叉树,(2)查找结点FindNode(*b,x)采用先序遍历递归算法查找值为x的结点。找到后返回其指针,否则返回NULL。算法如下:BTNode*FindNode(BTNode*b,ElemType x)/定义指针函数 BTNode*p;/定义搜索指针p if(b=NULL)return NULL;/判空树 else if(b-data=x)return b;else p=FindNode(b-lchild,x);if(p!=NULL)return p;else return FindNode(b-rchild,x);,(3)找孩子结点LchildNode(p
34、)和RchildNode(p)直接返回*p结点的左孩子结点或右孩子结点的指针。算法如下:BTNode*LchildNode(BTNode*p)return p-lchild;BTNode*RchildNode(BTNode*p)return p-rchild;,(4)求高度BTNodeDepth(*b)求二叉树的高度的递归模型f()如下:f(NULL)=0 f(b)=MAXf(b-lchild),f(b-rchild)+1 其他情况对应的算法如下:,int BTNodeDepth(BTNode*b)int lchilddep,rchilddep;if(b=NULL)return(0);/*空树
35、的高度为0*/else lchilddep=BTNodeDepth(b-lchild);/*求左子树的高度为lchilddep*/rchilddep=BTNodeDepth(b-rchild);/*求右子树的高度为rchilddep*/return(lchilddeprchilddep)?(lchilddep+1):(rchilddep+1);/条件运算符,(5)输出二叉树DispBTNode(*b)其过程是:对于非空二叉树b,先输出其元素值,当存在左孩子结点或右孩子结点时,输出一个“(”符号,然后递归处理左子树,输出一个“,”符号,递归处理右子树,最后输出一个“)”符号。对应的递归算法如下:
36、,void DispBTNode(BTNode*b)if(b!=NULL)printf(%c,b-data);if(b-lchild!=NULL|b-rchild!=NULL)printf();DispBTNode(b-lchild);/*递归处理左子树*/if(b-rchild!=NULL)printf(,);DispBTNode(b-rchild);/*递归处理右子树*/printf();,例7.3 假设二叉树采用二叉链存储结构,设计一个算法判断两棵二叉树是否相似,所谓二叉树t1和t2是相似的指的是t1和t2都是空的二叉树;或者t1和t2的根结点是相似的,以及t1的左子树和t2的左子树是相
37、似的且t1的右子树与t2的右子树是相似的。,解:判断两棵二叉树是否相似的递归模型f()如下:f(t1,t2)=true 若t1=t2=NULLf(t1,t2)=false 若t1、t2之一为NULL,另一不为NULLf(t1,t2)=f(t1-lchild,t2-lchild)&其他情况 f(t1-rchild,t2-rchild)对应的算法如下:,int Like(BTNode*b1,BTNode*b2)/定义返回int型值的函数/*t1和t2两棵二叉树相似时返回1,否则返回0*/int like1,like2;if(b1=NULL/*返回like1和like2的与*/左右子树分别相似,例7
38、.4 假设二叉树采用二叉链存储结构,设计一个算法Level()求二叉树中指定结点的层数。并利用本节的基本运算编写一个完整的程序,建立教材中图7.8(a)的二叉树的二叉链,对于用户输入的任何结点值计算出在该二叉树中的层次。,解:本题采用递归算法,设h返回p所指结点的高度,其初值为0。找到指定的结点时返回其层次;否则返回0。lh作为一个中间变量在计算搜索层次时使用,其初值为1。对应的算法如下:,int Level(BTNode*b,BTNode*p,int return h;,7.6 二叉树的构造,同一棵二叉树具有惟一先序序列、中序序列和后序序列,但不同的二叉树可能具有相同的先序序列、中序序列和后
39、序序列。例如,如教材中图7.9所示的5棵二叉树,先序序列都为ABC。如图7.10所示的5棵二叉树,中序序列都为ACB。如图7.11所示的5棵二叉树,后序序列都为CBA。,定理7.1:任何n(n0)个不同结点的二又树,都可由它的中序序列和先序序列惟一地确定定理7.2:任何n(n0)个不同结点的二又树,都可由它的中序序列和后序序列惟一地确定。,7.7 线索二叉树7.7.1 线索二叉树的概念 对于具有n个结点的二叉树,采用二叉链存储结构时,每个结点有两个指针域,总共有2n个指针域,又由于只有n-1个结点被有效指针所指向(n个结点中只有树根结点没有被有效指针域所指向),则共有2n-(n-1)=n+1个
40、空链域,我们知道,遍历二叉树的结果是一个结点的线性序列。可以利用这些空链域存放指向结点的前驱和后继结点的指针。这样的指向该线性序列中的“前驱”和“后继”的指针,称作线索。,若结点有左孩子,则Lchild指向其左孩子,否则,指向其直接前驱;若结点有右孩子,则Rchild指向其右孩子,否则,指向其直接后继;为避免混淆,对结点结构加以改进,增加两个标志域,如图所示。,按上述原则在二叉树的每个结点上加上线索的二叉树称作线索二叉树。对二叉树以某种方式遍历使其变为线索二叉树的过程称作按该方式对二叉树进行线索化。指向结点前驱和后继的指针叫做线索。,说明:画线索二叉树时,实线表示指针,指向其左、右孩子;虚线表
41、示线索,指向其直接前驱或直接后继。在线索树上进行遍历,只要先找到序列中的第一个结点,然后就可以依次找结点的直接后继结点直到后继为空为止。,7.7.2 线索化二叉树 建立线索二叉树,或者说,对二叉树线索化,实质上就是遍历一棵二叉树,在遍历的过程中,检查当前结点的左、右指针域是否为空。如果为空,将它们改为指向前驱结点或后继结点的线索。另外,在对一棵二叉树添加线索时,我们创建一个头结点,并建立头结点与二叉树的根结点的线索。对二叉树线索化后,还须建立最后一个结点与头结点之间的线索。下面以中序线索二叉树为例,讨论建立线索二叉树的算法。,仿照线性表的存储结构,在二叉树的线索链表上也添加一个头结点head,
42、头结点的指针域的安排是:Lchild域:指向二叉树的根结点;Rchild域:指向中序遍历时的最后一个结点;二叉树中序序列中的第一个结点Lchild指针域和最后一个结点Rchild指针域均指向头结点head。,为了实现线索化二叉树,将前面二叉树结点的类型定义修改如下:typedef struct node ElemType data;/*结点数据域*/int ltag,rtag;/*增加的线索标记*/struct node*lchild;/*左孩子或线索指针*/struct node*rchild;/*右孩子或线索指针*/TBTNode;/*线索树结点类型定义*/,下面是建立中序线索二叉树的算法
43、。Thread(p)算法用于对于以*p为根结点的二叉树中序线索化。在整个算法中p总是指向当前被线索化的结点,而pre作为全局变量,指向刚刚访问过的结点,*pre是*p的前驱结点,*p是*pre的后继结点。,Thread(p)算法思路是:类似于中序遍历的递归算法,在p指针不为NULL时,先对*p结点的左子树线索化;若*p结点没有左孩子结点,则将其lchild指针线索化为指向其前驱结点*pre,否则表示lchild指向其左孩子结点,将其ltag置为1;若*pre结点的rchild指针为NULL,将其rchild指针线索化为指向其后继结点*p,否则rchild表示指向其右孩子结点,将其rtag置为1
44、,再将pre指向*p;最后对*p结点的右子树线索化。,TBTNode*pre;/*全局变量*/void Thread(TBTNode*/*递归调用右子树线索化*/,中序遍历(递归),7.7.3 遍历线索化二叉树在线索二叉树中,由于有线索存在,在某些情况下可以方便地找到指定结点在某种遍历序列中的直接前驱或直接后继。此外,在线索二叉树上进行某种遍历比在一般的二叉树上进行这种遍历要容易得多,不需要设置堆栈,且算法十分简洁。,void inorder_Thread_bt(BiThrNode*T)BiThrNode*p;/定义线索化二叉树类型指针(搜索指针)if(T!=NULL)p=T;while(p-
45、Ltag=0)p=p-Lchild;/*寻找最左的结点*/while(p!=NULL)printf(%c,p-data);if(p-Rtag=1)p=p-Rchild;通过右线索找后继 else/*否则,找右子树的最左结点*/p=p-Rchild;while(p-Ltag=0)p=p-Lchild;,7.8 哈夫曼树,7.8.1 哈夫曼树的定义,7.8.2 构造哈夫曼树,7.8.3 哈夫曼编码,赫夫曼(Huffman)树又称最优树,是一类带权路径长度最短的树,有着广泛的应用。,最优二叉树(Huffman树),1 基本概念 结点路径:从树中一个结点到另一个结点的之间的分支构成这两个结点之间的路径
46、。路径长度:结点路径上的分支数目称为路径长度。树的路径长度:从树根到每一个结点的路径长度之和。,A到F:结点路径 AEF;路径长度(即边的数目)2;树的路径长度:31+52+23=19,结点的带权路径长度:从该结点的到树的根结点之间的路径长度与结点的权(值)的乘积。权(值):各种开销、代价、频度等的抽象称呼。树的带权路径长度:树中所有叶子结点的带权路径长度之和,记做:WPL=w1l1+w2l2+wnln=wili(i=1,2,n)其中:n为叶子结点的个数;wi为第i个结点的权值;li为第i个结点的路径长度。Huffman树:具有n个叶子结点(每个结点的权值为wi)的二叉树不止一棵,但在所有的这
47、些二叉树中,必定存在一棵WPL值最小的树,称这棵树为Huffman树(或称最优树)。,在许多判定问题时,利用Huffman树可以得到最佳判断算法。如图6-24是权值分别为2、3、6、7,具有4个叶子结点的二叉树,它们的带权路径长度分别为:(a)WPL=22+32+62+72=36;(b)WPL=21+32+63+73=47;(c)WPL=71+62+23+33=34。其中(c)的 WPL值最小,可以证明是Huffman树。,2 Huffman树的构造 根据n个权值w1,w2,wn,构造成n棵二叉树的集合F=T1,T2,Tn,其中每棵二叉树只有一个权值为wi的根结点,没有左、右子树;,在F中选取
48、两棵根结点权值最小的树作为左、右子树构造一棵新的二叉树,且新的二叉树根结点权值为其左、右子树根结点的权值之和;在F中删除这两棵树,同时将新得到的树加入F中;重复、,直到F只含一颗树为止。构造Huffman树时,为了规范,规定F=T1,T2,Tn中权值小的二叉树作为新构造的二叉树的左子树,权值大的二叉树作为新构造的二叉树的右子树;在取值相等时,深度小的二叉树作为新构造的二叉树的左子树,深度大的二叉树作为新构造的二叉树的右子树。,图6-25是权值集合W=8,3,4,6,5,5构造Huffman树的过程。所构造的Huffman树的WPL是:WPL=62+33+43+82+53+53=79,6.6.2
49、 赫夫曼编码及其算法,1 Huffman编码 在电报收发等数据通讯中,常需要将传送的文字转换成由二进制字符0、1组成的字符串来传输。为了使收发的速度提高,就要求电文编码要尽可能地短。此外,要设计长短不等的编码,还必须保证任意字符的编码都不是另一个字符编码的前缀,这种编码称为前缀编码。Huffman树可以用来构造编码长度不等且译码不产生二义性的编码。设电文中的字符集C=c1,c2,ci,cn,各个字符出现的次数或频度集W=w1,w2,wi,wn。,Huffman编码方法 以字符集C作为叶子结点,次数或频度集W作为结点的权值来构造 Huffman树。规定Huffman树中左分支代表“0”,右分支代
50、表“1”。从根结点到每个叶子结点所经历的路径分支上的“0”或“1”所组成的字符串,为该结点所对应的编码,称之为Huffman编码。由于每个字符都是叶子结点,不可能出现在根结点到其它字符结点的路径上,所以一个字符的Huffman编码不可能是另一个字符的Huffman编码的前缀。,若字符集C=a,b,c,d,e,f所对应的权值集合为W=8,3,4,6,5,5,如图6-25所示,则字符a,b,c,d,e,f所对应的Huffman编码分别是:10,010,011,00,110,111。,结点类型定义:#define MAX_NODE 200/*Max_Node2n-1*/typedef struct