版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
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来说没
2、有前驱结点,结点k0称作树的根。 (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) 树形表示法。这是树的最基本的表示,使用一棵倒置的树表示树结构,非常直观和形象。下图
3、就是采用这种表示法。,(2) 文氏图表示法。使用集合以及集合的包含关系描述树结构。下图就是树的文氏图表示法。,(3) 凹入表示法。使用线段的伸缩描述树结构。下图是树的凹入表示法。,(4) 括号表示法。将树的根结点写在括号的左边,除根结点之外的其余结点写在括号中并用逗号间隔来描述树结构。下图是树的括号表示法。,7.1.3 树的基本术语 1. 结点的度与树的度:树中某个结点的子树的个数称为该结点的度。树中各结点的度的最大值称为树的度,通常将度为m的树称为m次树。 2. 分支结点与叶结点:度不为零的结点称为非终端结点,又叫分支结点。度为零的结点称为终端结点或叶结点。在分支结点中,每个结点的分支数就是
4、该结点的度。如对于度为1的结点,其分支数为1,被称为单分支结点;对于度为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。 证明:根据树的定义,在一棵树中,除树根结点外,每个结点有且仅有一个前驱结点。也就是说,每个结点与指向它的一个分支一一对应,所以除树根之外的结点数等于所有结点的分支数(度数),从而可得树中的结点数等于所有结点
7、的度数加1。,性质2 度为m的树中第i层上至多有mi-1个结点,这里应有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层上最
8、多结点数为mi-1(i=1,2,h),显然当高度为h的m次树(即度为m的树)上每一层都达到最多结点数时,整个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-
9、1logm(n(m-1)+1)h 即logm(n(m-1)+1)hlogm(n(m-1)+1)+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 树的基本运算,树的运算主要分为三大类: 第一类,寻找满足某种特定关系的结点,如寻找当前结点的双亲结点等; 第二类,插入
10、或删除某个结点,如在树的当前结点上插入一个新结点或删除当前结点的第i个孩子结点等; 第三类,遍历树中每个结点,这里着重介绍。,树的遍历运算是指按某种方式访问树中的每一个结点且每一个结点只被访问一次。树的遍历运算的算法主要有先根遍历和后根遍历两种。注意,下面的先根遍历和后根遍历算法都是递归的。 1. 先根遍历 先根遍历过程为: (1) 访问根结点; (2) 按照从左到右的次序先根遍历根结点的每一棵子树。,2. 后根遍历 后根遍历过程为: (1) 按照从左到右的次序后根遍历根结点的每一棵子树; (2) 访问根结点。,7.1.6 树的存储结构,1. 双亲存储结构 这种存储结构是一种顺序存储结构,用一
11、组连续空间存储树的所有结点,同时在每个结点中附设一个伪指针指示其双亲结点的位置。,树的双亲存储结构示意图,2. 孩子链存储结构 孩子链存储结构可按树的度(即树中所有结点度的最大值)设计结点的孩子结点指针域个数。下图(a)的树对应的孩子链存储结构如图(b)所示。,树的孩子链存储结构示意图,3. 孩子兄弟链存储结构 孩子兄弟链存储结构是为每个结点设计三个域:一个数据元素域,一个该结点的第一个孩子结点指针域,一个该结点的下一个兄弟结点指针域。下图(a)的树对应的孩子兄弟链存储结构如图(b)所示。,树的孩子兄弟链存储结构示意图,7.2 二叉树概念和性质,7.2.1 二叉树概念,7.2.2 二叉树性质,
12、7.2.3 二叉树与树、森林之间的转换,7.2.1 二叉树概念 二叉树也称为二次树或二分树,它是有限的结点集合,这个集合或者是空,或者由一个根结点和两棵互不相交的称为左子树和右子树的二叉树组成。 二叉树的定义是一种递归定义。,二叉树有五种基本形态,如下图所示,任何复杂的二叉树都是这五种基本形态的复合。,从定义看到,二叉树是一种特殊的树,其表示法也与树的表示法一样,有树形表示法、文氏图表示法、凹入表示法和括号表示法等。,在一棵二叉树中,如果所有分支结点都有左孩子结点和右孩子结点,并且叶结点都集中在二叉树的最下一层,这样的二叉树称为满二叉树。下图所示就是一棵满二叉树。可以对满二叉树的结点进行连续编
13、号,约定编号从树根为1开始,按照层数从小到大、同一层从左到右的次序进行。图中每个结点外边的数字为对该结点的编号。,满二叉树的特点: 基本特点是每一层上的结点数总是最大结点数。 满二叉树的所有的支结点都有左、右子树。 可对满二叉树的结点进行连续编号,若规定从根结点开始,按“自上而下、自左至右”的原则进行,若二叉树中最多只有最下面两层的结点的度数可以小于2,并且最下面一层的叶结点 都依次排列在该层最左边的位置上,则这样的二叉树称为完全二叉树。如下图所示为一棵完全二叉树。同样可以对完全二叉树中每个结点进行连续编号,编号的方法同满二叉树相同。图中每个结点外边的数字为对该结点的编号。,完全二叉树是满二叉
14、树的一部分,而满二叉树是完全二叉树的特例。 完全二叉树的特点: 若完全二叉树的深度为k ,则所有的叶子结点都出现在第k层或k-1层。对于任一结点,如果其右子树的最大层次为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 +
15、1。 其中符号: x表示不大于x的最大整数。(上口大) x 表示不小于x的最小整数。(上口小) 书上二叉树的性质,性质5:若对一棵有n个结点的完全二叉树(深度为2n+1)的结点按层(从第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
17、且只有k个结点的单支树(二叉树每个结点只有一个孩子)需要长度为2k-1的一维数组。,7.3.2 二叉树的链式存储结构 在二叉树的链接存储中,结点的结构如下: typedef struct node ElemType data; struct node *lchild,*rchild; BTNode; 其中,data表示值域,用于存储对应的数据元素,lchild和rchild分别表示左指针域和右指针域,用于分别存储左孩子结点和右孩子结点(即左、右子树的根结点)的存储位置。,二叉树及其链式存储结构,7.4 二叉树的遍历,7.4.1 二叉树遍历的概念,7.4.2 二叉树遍历递归算法,7.4.3 二叉
18、树遍历非递归算法,7.4.1 二叉树遍历的概念 二叉树的遍历是指按照一定次序访问树中所有结点,并且每个结点仅被访问一次的过程。它是最基本的运算,是二叉树中所有其他运算的基础。,1. 先序遍历(先中后相对于根结点,也即前序遍历) 先序遍历二叉树的过程是: (1) 访问根结点; (2) 先序遍历左子树; (3) 先序遍历右子树。 2. 中序遍历 中序遍历二叉树的过程是: (1) 中序遍历左子树; (2) 访问根结点; (3) 中序遍历右子树。,3. 后序遍历 后序遍历二叉树的过程是: (1) 后序遍历左子树; (2) 后序遍历右子树; (3) 访问根结点。,(1) 先序遍历:root-leftch
19、ild-rightchild,6.3 遍历二叉树,先序遍历序列:A B D C,(2) 中序遍历:leftchild-root-rightchild,中序遍历序列:B D A C,6.3 遍历二叉树,(3) 后序遍历:leftchild-rightchild-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/
20、f 其后序序列为: abcd-*+ef/-,(5) 三种遍历的相互转换: 已知先序遍历与后序遍历,求中序遍历;(不唯一) 已知先序遍历与中序遍历,求后序遍历;(唯一) 已知中序遍历和后序遍历,求先序遍历。(唯一),6.3 遍历二叉树,例1: 中序遍历序列为:badcfeg 后序遍历序列为:bdfgeca 先序遍历序列为? abcdefg,6.3 遍历二叉树,例2: 先序遍历为:12345678; 中序遍历为:32416587; 后序遍历为? 34268751,6.3 遍历二叉树,例3: 先序遍历为:ABCDEGF; 后序遍历为:CGEFDBA; 中序遍历为? CBEGDFA,6.3 遍历二叉树
21、,对于二叉树的遍历,分别讨论递归遍历算法和非递归遍历算法。递归遍历算法具有非常清晰的结构,但初学者往往难以接受或怀疑,不敢使用。实际上,递归算法是由系统通过使用堆栈来实现控制的。而非递归算法中的控制是由设计者定义和使用堆栈来实现的。,7.4.2 二叉树遍历递归算法 1.先序遍历的递归算法 算法的递归定义是: 若二叉树为空,则遍历结束;否则 访问根结点; 先序遍历左子树(递归调用本算法); 先序遍历右子树(递归调用本算法)。,void PreOrder(BTNode *b) /*先序遍历的递归算法*/ if (b!=NULL) printf(%c ,b-data); /*访问根结点*/ PreO
22、rder(b-lchild); PreOrder(b-rchild); ,2.中序遍历的递归算法 算法的递归定义是: 若二叉树为空,则遍历结束;否则 中序遍历左子树(递归调用本算法); 访问根结点; 中序遍历右子树(递归调用本算法)。,void InOrder(BTNode *b) /*中序遍历的递归算法*/ if (b!=NULL) InOrder(b-lchild); printf(%c ,b-data); /*访问根结点*/ InOrder(b-rchild); ,3.后序遍历的递归算法 算法的递归定义是: 若二叉树为空,则遍历结束;否则 后序遍历左子树(递归调用本算法); 后序遍历右子
23、树(递归调用本算法) ; 访问根结点 。,void PostOrder(BTNode *b) /*后序遍历递归算法*/ if (b!=NULL) PostOrder(b-lchild); PostOrder(b-rchild); printf(%c ,b-data); /*访问根结点*/ ,7.4.3 二叉树遍历非递归算法,1. 先序遍历非递归算法 设T是指向二叉树根结点的指针变量,非递归算法是: 若二叉树为空,则返回;否则,令p=T; 访问p所指向的结点; q=p-Rchild ,若q不为空,则q进栈; p=p-Lchild ,若p不为空,转(1),否则转(4); 退栈到p ,转(1),直到
24、栈空为止。,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)/左孩子结点入栈 top+; Sttop=p-lchild; ,2. 中序遍历非递归算法 (2)第二种方法(常规方法) 由中序遍历过程可知,采用一个
25、栈保存需要返回的结点指针,先扫描(并非访问)根结点的所有左结点并将它们一一进栈。 然后出栈一个结点,显然该结点没有左孩子结点或者左孩子结点已访问过(进一步说明该结点的左子树均已访问),则访问它。然后扫描该结点的右孩子结点,将其进栈,再扫描该右孩子结点的所有左结点并一一进栈,如此这样,直到栈空为止。,设T是指向二叉树根结点的指针变量,非递归算法是: 若二叉树为空,则返回;否则,令p=T 若p不为空,p进栈, p=p-Lchild ; 否则(即p为空),退栈到p,访问p所指向的结点; p=p-Rchild ,转(1); 直到栈空为止。,void InOrder2(BTNode *b) BTNode
26、 *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. 后序遍历非递归算法 在后序遍历中,根结点是最后被访问的。因此,在遍历过程中,当搜索指针指向某一根结点时,不能立即访问
27、,而要先遍历其左子树,此时根结点进栈。当其左子树遍历完后再搜索到该根结点时,还是不能访问,还需遍历其右子树。所以,此根结点还需再次进栈,当其右子树遍历完后再退栈到到该根结点时,才能被访问。 因此,设立一个状态标志变量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指向栈顶结点的前一个已访问的结点 flag=1; /设置b的访问
28、标记为已访问过,找最左下结点,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,rea
29、r;/*定义队头和队尾指针*/ 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)%MaxSiz
30、e; qurear=p-rchild; ,例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 二叉树的基本
31、运算概述 归纳起来,二叉树有以下基本运算: (1)创建二叉树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):以括号表示法输出
32、一棵二叉树。,7.5.2 二叉树的基本运算算法实现 (1) 创建二叉树CreateBTNode(*b,*str) 用ch扫描采用括号表示法表示二叉树的字符串。分以下几种情况: 若ch=(:则将前面刚创建的结点作为双亲结点进栈,并置k=1,表示其后创建的结点将作为这个结点的左孩子结点; 若ch=):表示栈中结点的左右孩子结点处理完毕,退栈; 若ch=,:表示其后创建的结点为右孩子结点;, 其他情况,表示要创建一个结点,并根据k值建立它与栈中结点之间的联系,当k=1时,表示这个结点作为栈中结点的左孩子结点,当k=2时,表示这个结点作为栈中结点的右孩子结点。如此循环直到str处理完毕。算法中使用一个
33、栈St保存双亲结点,top为其栈指针,k指定其后处理的结点是双亲结点(保存在栈中)的左孩子结点(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;
34、 j+;ch=strj; ,例如,对于括号表示串A(B(D(,G),C(E,F),建立二叉树链式存储结构的过程如下表所示。,生成的二叉树,(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!=NUL
35、L) return p; else return FindNode(b-rchild,x); ,(3) 找孩子结点LchildNode(p)和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 其他情况
36、 对应的算法如下:,int BTNodeDepth(BTNode *b) int lchilddep,rchilddep; if (b=NULL) return(0); /*空树的高度为0*/ else lchilddep=BTNodeDepth(b-lchild); /*求左子树的高度为lchilddep*/ rchilddep=BTNodeDepth(b-rchild); /*求右子树的高度为rchilddep*/ return(lchilddeprchilddep)? (lchilddep+1):(rchilddep+1); /条件运算符 ,(5) 输出二叉树DispBTNode(*b)
37、 其过程是:对于非空二叉树b,先输出其元素值,当存在左孩子结点或右孩子结点时,输出一个“(”符号,然后递归处理左子树,输出一个“,”符号,递归处理右子树,最后输出一个“)”符号。对应的递归算法如下:,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); /*递归处理右
38、子树*/ printf(); ,例7.3 假设二叉树采用二叉链存储结构,设计一个算法判断两棵二叉树是否相似,所谓二叉树t1和t2是相似的指的是t1和t2都是空的二叉树;或者t1和t2的根结点是相似的,以及t1的左子树和t2的左子树是相似的且t1的右子树与t2的右子树是相似的。,解:判断两棵二叉树是否相似的递归模型f()如下: f(t1,t2)=true 若t1=t2=NULL f(t1,t2)=false 若t1、t2之一为NULL,另一不为NULL f(t1,t2)=f(t1-lchild,t2-lchild) if (b1=NULL /*返回like1和like2的与*/ /左右子树分别相
39、似 ,例7.4 假设二叉树采用二叉链存储结构,设计一个算法Level()求二叉树中指定结点的层数。并利用本节的基本运算编写一个完整的程序,建立教材中图7.8(a)的二叉树的二叉链,对于用户输入的任何结点值计算出在该二叉树中的层次。,解:本题采用递归算法,设h返回p所指结点的高度,其初值为0。找到指定的结点时返回其层次;否则返回0。lh作为一个中间变量在计算搜索层次时使用,其初值为1。对应的算法如下:,int Level(BTNode *b,BTNode *p,int return h; ,7.6 二叉树的构造,同一棵二叉树具有惟一先序序列、中序序列和后序序列,但不同的二叉树可能具有相同的先序序
40、列、中序序列和后序序列。 例如,如教材中图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
41、-(n-1)=n+1个空链域, 我们知道,遍历二叉树的结果是一个结点的线性序列。可以利用这些空链域存放指向结点的前驱和后继结点的指针。这样的指向该线性序列中的“前驱”和“后继”的指针,称作线索。, 若结点有左孩子,则Lchild指向其左孩子,否则,指向其直接前驱; 若结点有右孩子,则Rchild指向其右孩子,否则,指向其直接后继; 为避免混淆,对结点结构加以改进,增加两个标志域,如图所示。,按上述原则在二叉树的每个结点上加上线索的二叉树称作线索二叉树。对二叉树以某种方式遍历使其变为线索二叉树的过程称作按该方式对二叉树进行线索化。 指向结点前驱和后继的指针叫做线索。,说明:画线索二叉树时,实线表
42、示指针,指向其左、右孩子;虚线表示线索,指向其直接前驱或直接后继。 在线索树上进行遍历,只要先找到序列中的第一个结点,然后就可以依次找结点的直接后继结点直到后继为空为止。,7.7.2 线索化二叉树 建立线索二叉树,或者说,对二叉树线索化,实质上就是遍历一棵二叉树,在遍历的过程中,检查当前结点的左、右指针域是否为空。如果为空,将它们改为指向前驱结点或后继结点的线索。另外,在对一棵二叉树添加线索时,我们创建一个头结点,并建立头结点与二叉树的根结点的线索。对二叉树线索化后,还须建立最后一个结点与头结点之间的线索。 下面以中序线索二叉树为例,讨论建立线索二叉树的算法。,仿照线性表的存储结构,在二叉树的
43、线索链表上也添加一个头结点head,头结点的指针域的安排是: Lchild域:指向二叉树的根结点; Rchild域:指向中序遍历时的最后一个结点; 二叉树中序序列中的第一个结点Lchild指针域和最后一个结点Rchild指针域均指向头结点head。,为了实现线索化二叉树,将前面二叉树结点的类型定义修改如下: typedef struct node ElemType data;/*结点数据域*/ int ltag,rtag; /*增加的线索标记*/ struct node *lchild;/*左孩子或线索指针*/ struct node *rchild;/*右孩子或线索指针*/ TBTNode;
44、/*线索树结点类型定义*/,下面是建立中序线索二叉树的算法。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
45、,否则rchild表示指向其右孩子结点,将其rtag置为1,再将pre指向*p;最后对*p结点的右子树线索化。,TBTNode *pre; /*全局变量*/ void Thread(TBTNode * /*递归调用右子树线索化*/ ,中序遍历(递归),7.7.3 遍历线索化二叉树 在线索二叉树中,由于有线索存在,在某些情况下可以方便地找到指定结点在某种遍历序列中的直接前驱或直接后继。此外,在线索二叉树上进行某种遍历比在一般的二叉树上进行这种遍历要容易得多,不需要设置堆栈,且算法十分简洁。,void inorder_Thread_bt(BiThrNode *T) BiThrNode *p ; /
46、定义线索化二叉树类型指针(搜索指针) if (T!=NULL) p=T; while (p-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)树又称最优树,是一类带权路径长度最短的树,
47、有着广泛的应用。,最优二叉树(Huffman树),1 基本概念 结点路径:从树中一个结点到另一个结点的之间的分支构成这两个结点之间的路径。 路径长度:结点路径上的分支数目称为路径长度。 树的路径长度:从树根到每一个结点的路径长度之和。,A到F :结点路径 AEF ;路径长度(即边的数目) 2 ;树的路径长度:31+52+23=19, 结点的带权路径长度:从该结点的到树的根结点之间的路径长度与结点的权(值)的乘积。 权(值):各种开销、代价、频度等的抽象称呼。 树的带权路径长度:树中所有叶子结点的带权路径长度之和,记做: WPL=w1l1+w2l2+wnln=wili (i=1,2,n) 其中:
48、n为叶子结点的个数;wi为第i个结点的权值; li为第i个结点的路径长度。 Huffman树:具有n个叶子结点(每个结点的权值为wi) 的二叉树不止一棵,但在所有的这些二叉树中,必定存在一棵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值最小,可以证明是H
49、uffman树。,2 Huffman树的构造 根据n个权值w1, w2, ,wn,构造成n棵二叉树的集合F=T1, T2, ,Tn,其中每棵二叉树只有一个权值为wi的根结点,没有左、右子树;, 在F中选取两棵根结点权值最小的树作为左、右子树构造一棵新的二叉树,且新的二叉树根结点权值为其左、右子树根结点的权值之和; 在F中删除这两棵树,同时将新得到的树加入F中; 重复、,直到F只含一颗树为止。 构造Huffman树时,为了规范,规定F=T1,T2, ,Tn中权值小的二叉树作为新构造的二叉树的左子树,权值大的二叉树作为新构造的二叉树的右子树;在取值相等时,深度小的二叉树作为新构造的二叉树的左子树,
50、深度大的二叉树作为新构造的二叉树的右子树。,图6-25是权值集合W=8, 3, 4, 6, 5, 5构造Huffman树的过程。所构造的Huffman树的WPL是: WPL=62+33+43+82+53+53 =79,6.6.2 赫夫曼编码及其算法,1 Huffman编码 在电报收发等数据通讯中,常需要将传送的文字转换成由二进制字符0、1组成的字符串来传输。为了使收发的速度提高,就要求电文编码要尽可能地短。此外,要设计长短不等的编码,还必须保证任意字符的编码都不是另一个字符编码的前缀,这种编码称为前缀编码。 Huffman树可以用来构造编码长度不等且译码不产生二义性的编码。 设电文中的字符集C
51、=c1,c2, ,ci, ,cn,各个字符出现的次数或频度集W=w1,w2, ,wi, ,wn。,Huffman编码方法 以字符集C作为叶子结点,次数或频度集W作为结点的权值来构造 Huffman树。规定Huffman树中左分支代表“0”,右分支代表“1” 。 从根结点到每个叶子结点所经历的路径分支上的“0”或“1”所组成的字符串,为该结点所对应的编码,称之为Huffman编码。 由于每个字符都是叶子结点,不可能出现在根结点到其它字符结点的路径上,所以一个字符的Huffman编码不可能是另一个字符的Huffman编码的前缀。,若字符集C=a, b, c, d, e, f所对应的权值集合为W=8
52、, 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 unsigned int Weight ; /* 权值域 */ unsinged int Parent , Lchild , Rchild ; HTNode ;,2 Huffman编码算法实现 (1) 数据结构设计,Huffman树中有n个叶子结点, Huffman树共有2n-1个结点,则可存储在大小为2n-
53、1的一维数组中。,(2) Huffman树的生成 算法实现 void Create_Huffman(unsigned n, HTNode HT , unsigned m) /* 创建一棵叶子结点数为n的Huffman树 */ unsigned int w ; int k , j ; for (k=1 ; km ; k+) if (k=n) printf(“n Please Input Weight : w=?”); scanf(“%d”, /* 非叶子结点没有权值 */,HTk.Parent=HTk.Lchild=HTk.Rchild=0 ; /* 初始化向量HT */ for (k=n+1;
54、 km ; k+) unsigned w1=32767 , w2=w1 ; /* w1 , w2分别保存权值最小的两个权值 */ int p1=0 , p2=0 ; /* p1 , p2保存两个最小权值的下标 */ for (j=1 ; j=k-1 ; j+) if (HTk.Parent=0) /* 尚未合并 */ if (HTj.Weightw1) w2=w1 ; p2=p1 ; w1=HTj.Weight ; p1=j ; ,else if (HTj.Weightw2) w2=HTj.Weight ; p2=j ; /* 找到权值最小的两个值及其下标 */ HTk.Lchild=p1 ;
55、 HTk.Rchild=p2 ; HTk.weight=w1+w2 ; HTp1.Parent=k ; HTp2.Parent=k ; 说明:生成Huffman树后,树的根结点的下标是2n-1 ,即m-1 。,(3) Huffman编码算法 根据出现频度(权值)Weight,对叶子结点的Huffman编码有两种方式: 从叶子结点到根逆向处理,求得每个叶子结点对应字符的Huffman编码。 从根结点开始遍历整棵二叉树,求得每个叶子结点对应字符的Huffman编码。 由Huffman树的生成知,n个叶子结点的树共有2n-1个结点,叶子结点存储在数组HT中的下标值为1n。 编码是叶子结点的编码,只需
56、对数组HT1n的n个权值进行编码; 每个字符的编码不同,但编码的最大长度是n。,求编码时先设一个通用的指向字符的指针变量,求得编码后再复制。 算法实现 void Huff_coding(unsigned n , Hnode HT , unsigned m) /* m应为n+1,编码的最大长度n加1 */ int k , sp ,fp ; char *cd , *HCm ; cd=(char *)malloc(m*sizeof(char) ; /* 动态分配求编码的工作空间 */ cdn=0 /* 编码的结束标志 */ for (k=1 ; kn+1 ; k+) /* 逐个求字符的编码 */ s
57、p=n ; p=k ; fp=HTk.parent ;,for ( ; fp!=0 ;p=fp , fp=HTp.parent) /* 从叶子结点到根逆向求编码 */ if (HTfp.parent=p) cd-sp=0 ; else cd-sp=1 ; HCk=(char *)malloc(n-sp)*sizeof(char) ; /* 为第k个字符分配保存编码的空间 */ trcpy(HCk , ,1 树转换成二叉树 对于一般的树,可以方便地转换成一棵唯一的二叉树与之对应。将树转换成二叉树在“孩子兄弟表示法”中已给出,其详细步骤是: 加虚线。在树的每层按从“左至右”的顺序在兄弟结点之间加虚线相连。 去连线。除最左的第一个子结点外,父结点与所有其它子结点的连线都去掉。 旋转。将树顺时针旋转450,原有的实线
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025年四川七年级英语下册期末统考试卷及答案
- 民族团结宣传教育工作方案
- 新食品安全法试题及答案
- 乡镇(街道)社会工作服务站建设细则
- 2026年老年人噎食应急处置技能鉴定考试题
- 食品集团原料采购准则
- 数字化转型进程中网络安全防护体系构建与韧性提升路径研究
- 全球数字化转型的演进趋势与综合影响分析
- 产业元宇宙技术创新与产业化应用前景研究
- 分布式算力调度模式及其在资源优化中的应用
- 美的集团第-级公司分权手册
- 网络传播概论(彭兰第5版) 课件全套 第1-8章 网络媒介的演变-网络传播中的“数字鸿沟”
- LY/T 1575-2023汽车车厢底板用竹胶合板
- 被执行人生活费申请书范文
- KWFB无密封自控自吸泵(服务)课件
- 浙江绍兴杭绍临空示范区开发集团有限公司招聘15名企业合同制人员模拟预测(共500题)笔试参考题库附答案详解
- GB/T 3098.15-2023紧固件机械性能不锈钢螺母
- 发展经济学 马工程课件 7.第七章 工业化与信息化
- 新《义务教育法》解读
- 第一章 血液学绪论
- 《审计法》PPT课件资料
评论
0/150
提交评论