资源描述
第六章树和二叉树,6.1树的类型定义,6.2二叉树的类型定义,6.3二叉树的存储结构,6.4二叉树的遍历,6.5线索二叉树,6.6树和森林的表示方法,6.7树和森林的遍历,6.8哈夫曼树与哈夫曼编码,目录,6.1树的类型定义,数据对象D:,D是具有相同特性的数据元素的集合。,若D为空集,则称为空树;否则:(1)在D中存在唯一的称为根的数据元素root,(2)当n1时,其余结点可分为m(m0)个互不相交的有限集T1,T2,Tm,其中每一个子集本身又是一棵符合本定义的树,称为根root的子树。,数据关系R:,ADT,2020/7/4,5,树的示例,基本操作:,查找类,插入类,删除类,Root(T)/求树的根结点,Value(T,cur_e)/求当前结点的元素值,Parent(T,cur_e)/求当前结点的双亲结点,LeftChild(T,cur_e)/求当前结点的最左孩子,RightSibling(T,cur_e)/求当前结点的右兄弟,TreeEmpty(T)/判定树是否为空树,TreeDepth(T)/求树的深度,TraverseTree(T,Visit()/遍历,查找类操作,InitTree(Value(T,e);Parent(T,e);LeftChild(T,e);RightChild(T,e);LeftSibling(T,e);RightSibling(T,e);BiTreeEmpty(T);BiTreeDepth(T);PreOrderTraverse(T,Visit();InOrderTraverse(T,Visit();PostOrderTraverse(T,Visit();LevelOrderTraverse(T,Visit();,查找类操作,InitBiTree(,插入类操作,ClearBiTree(,删除类操作,二叉树的重要特性,性质1:在二叉树的第i层上至多有2i-1个结点。(i1),用归纳法证明:归纳基:归纳假设:归纳证明:,i=1层时,只有一个根结点,2i-1=20=1;,假设对所有的j,1ji,命题成立;,二叉树上每个结点至多有两棵子树,则第i层的结点数=2i-22=2i-1。,性质2:深度为k的二叉树上至多含2k-1个结点(k1),证明:,基于上一条性质,深度为k的二叉树上的结点数至多为20+21+2k-1=2k-1,性质3:对任何一棵二叉树,若它含有n0个叶子结点、n2个度为2的结点,则必存在关系式:n0=n2+1,证明:,设二叉树上结点总数n=n0+n1+n2又二叉树上分支总数b=n1+2n2而b=n-1=n0+n1+n2-1由此,n0=n2+1,满二叉树:深度为k且含有2k-1个结点的二叉树。,完全二叉树:树中所含的n个结点和满二叉树中编号为1至n的结点一一对应。,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,a,b,c,d,e,f,g,h,i,j,两类特殊的二叉树:,证明:,设完全二叉树的深度为k则根据第二条性质得2k-1n2k即k-1log2nk因为k只能是整数,因此,k=log2n+1,性质4:具有n个结点的完全二叉树的深度为log2n+1,若对含n个结点的完全二叉树从上到下且从左至右进行1至n的编号,则对完全二叉树中任意一个编号为i的结点:(1)若i=1,则该结点是二叉树的根,无双亲,否则,编号为i/2的结点为其双亲结点;(2)若2in,则该结点无左孩子,否则,编号为2i的结点为其左孩子结点;(3)若2i+1n,则该结点无右孩子结点,否则,编号为2i+1的结点为其右孩子结点。,性质5:,二.二叉树的链式存储表示,一.二叉树的顺序存储表示,6.3二叉树的存储结构,#defineMAX_TREE_SIZE100/二叉树的最大结点数typedefTElemTypeSqBiTreeMAX_TREE_SIZE;/1号单元存储根结点SqBiTreebt;,一.二叉树的顺序存储表示,ABDCEF,A,B,C,D,E,F,01234567891011121314,2,5,1,14,3,7,例如:,1.二叉链表,2三叉链表,3双亲链表,4线索链表,二.二叉树的链式存储表示,A,D,E,B,C,F,root,lchilddatarchild,结点结构:,1.二叉链表,typedefstructBiTNode/结点结构TElemTypedata;structBiTNode*lchild,*rchild;/左、右孩子指针BiTNode,*BiTree;,lchilddatarchild,结点结构:,C语言的类型描述如下:,A,D,E,B,C,F,root,parentlchilddatarchild,结点结构:,2三叉链表,typedefstructTriTNode/结点结构TElemTypedata;structTriTNode*lchild,*rchild;/左、右孩子指针structTriTNode*parent;/双亲指针TriTNode,*TriTree;,parentlchilddatarchild,结点结构:,C语言的类型描述如下:,LRRRL,0123456,dataparent,结点结构:,LRTag,3双亲链表,typedefstructBPTNode/结点结构TElemTypedata;intparent;/指向双亲的指针charLRTag;/左、右孩子标志域BPTNodetypedefstructBPTree/树结构BPTNodenodesMAX_TREE_SIZE;intnum_node;/结点数目introot;/根结点的位置BPTree,6.4二叉树的遍历,一、问题的提出,二、先左后右的遍历算法,三、算法的递归描述,四、中序遍历算法的非递归描述,五、遍历算法的应用举例,顺着某一条搜索路径巡访二叉树中的结点,使得每个结点均被访问一次,而且仅被访问一次。,一.问题的提出,“访问”的含义可以很广,如:输出结点的信息等。,“遍历”是任何类型均有的操作,对线性结构而言,只有一条搜索路径(因为每个结点均只有一个后继),故不需要另加讨论。而二叉树是非线性结构,每个结点有两个后继,则存在如何遍历即按什么样的搜索路径遍历的问题。,对“二叉树”而言,可以有三条搜索路径:,1先上后下的按层次遍历;2先左(子树)后右(子树)的遍历;3先右(子树)后左(子树)的遍历。,先(根)序的遍历算法,中(根)序的遍历算法,后(根)序的遍历算法,二.先左后右的遍历算法,若二叉树为空树,则空操作;否则,(1)访问根结点;(2)先序遍历左子树;(3)先序遍历右子树。,先(根)序的遍历算法:,若二叉树为空树,则空操作;否则,(1)中序遍历左子树;(2)访问根结点;(3)中序遍历右子树。,中(根)序的遍历算法:,若二叉树为空树,则空操作;否则,(1)后序遍历左子树;(2)后序遍历右子树;(3)访问根结点。,后(根)序的遍历算法:,voidPreorder(BiTreeT,void(*visit)(TElemType/遍历右子树,三.算法的递归描述,BiTNode*GoFarLeft(BiTreeT,Stack,四.中序遍历算法的非递归描述,voidInorder_I(BiTreeT,void(*visit)(TelemType/栈空表明遍历结束,1.统计二叉树中叶子结点的个数(先序遍历,习题6.42),2.求二叉树的深度(后序遍历,参见习题6.44),3.复制二叉树(后序遍历,参见习题6.46),4.建立二叉树的存储结构,五.遍历算法的应用举例,算法基本思想:,先序(或中序或后序)遍历二叉树,在遍历过程中查找叶子结点,并计数。因此,需在遍历算法中增添一个“计数”的参数,并将算法中“访问结点”的操作改为:若是叶子,则计数器增1。,1.统计二叉树中叶子结点的个数(习题6.42),voidCountLeaf(BiTreeT,int,调用:num0;CountLeaf(T,num);,算法基本思想:,从二叉树深度的定义可知,二叉树的深度应为其左、右子树深度的最大值加1。由此,需先分别求得左、右子树的深度,算法中“访问结点”的操作为:求得左、右子树深度的最大值,然后加1。,首先分析二叉树的深度和它的左、右子树深度之间的关系。,2.求二叉树的深度(后序遍历)(参见习题6.44),intDepth(BiTreeT)/返回二叉树的深度if(!T)depthval=0;elsedepthLeft=Depth(T-lchild);depthRight=Depth(T-rchild);depthval=1+(depthLeftdepthRight?depthLeft:depthRight);returndepthval;,其基本操作为:生成一个结点。,根元素,T,左子树,右子树,根元素,NEWT,左子树,右子树,左子树,右子树,(后序遍历),3.复制二叉树(参见习题6.46),BiTNode*GetTreeNode(TElemTypeitem,BiTNode*lptr,BiTNode*rptr)if(!(T=(BiTNode*)malloc(sizeof(BiTNode)exit(1);T-data=item;T-lchild=lptr;T-rchild=rptr;returnT;,生成二叉树的一个结点(其数据域为item,左指针域为lptr,右指针域为rptr),BiTNode*CopyTree(BiTNode*T)if(!T)returnNULL;if(T-lchild)newlptr=CopyTree(T-lchild);/复制左子树elsenewlptr=NULL;if(T-rchild)newrptr=CopyTree(T-rchild);/复制右子树elsenewrptr=NULL;newT=GetTreeNode(T-data,newlptr,newrptr);returnnewT;问题:,若用先序框架,应如何修改?,A,B,C,D,E,F,G,H,K,D,C,B,H,K,G,F,E,A,例如:下列二叉树的复制过程如下:,newT,4.建立二叉树的存储结构,不同的定义方法相应有不同的存储结构的建立算法,以字符串的形式根左子树右子树定义一棵二叉树,例如:,A,B,C,D,以空白字符“”表示,A(B(,C(,),D(,),空树,只含一个根结点的二叉树,A,以字符串“A”表示,以下列字符串表示,StatusCreateBiTree(BiTree,ABCD,A,B,C,D,上页算法执行过程举例如下:,A,T,B,C,D,按给定的表达式建相应二叉树,由先缀表示式建树例如:已知表达式的先缀表示式-+abc/de,由原表达式建树例如:已知表达式(a+b)cd/e,对应先缀表达式-+abc/de的二叉树,a,b,c,d,e,-,+,/,特点:操作数为叶子结点,运算符为分支结点,scanf(,由先缀表示式建树的算法的基本操作:,a+b,(a+b)cd/e,a+bc,分析表达式和二叉树的关系:,a,b,+,a,b,c,+,a,b,c,+,(a+b)c,a,b,c,d,e,-,+,/,基本操作:,scanf(,voidCrtExptree(BiTree,switch(ch)case(:Push(S,ch);break;case):Pop(S,c);while(c!=()CrtSubtree(t,c);/建二叉树并入栈Pop(S,c);break;defult:/switch,while(!Gettop(S,c),建叶子结点的算法为:,voidCrtNode(BiTree,建子树的算法为:,voidCrtSubtree(Bitree,仅知二叉树的先序序列“abcdefg”不能唯一确定一棵二叉树,,由二叉树的先序和中序序列建树,如果同时已知二叉树的中序序列“cbdaegf”,则会如何?,二叉树的先序序列,二叉树的中序序列,左子树,左子树,右子树,右子树,根,根,abcdefg,cbdaegf,例如:,a,a,b,b,c,c,d,d,e,e,f,f,g,g,a,b,c,d,e,f,g,先序序列中序序列,voidCrtBT(BiTreeelse,T=(BiTNode*)malloc(sizeof(BiTNode);T-data=preps;if(k=is)T-Lchild=NULL;elseCrtBT(T-Lchild,pre,ino,ps+1,is,k-is);if(k=is+n-1)T-Rchild=NULL;elseCrtBT(T-Rchild,pre,ino,ps+1+(k-is),k+1,n-(k-is)-1);,6.5线索二叉树,何谓线索二叉树?线索链表的遍历算法如何建立线索链表?,遍历二叉树的结果是,求得结点的一个线性序列。,A,B,C,D,E,F,G,H,K,例如:,先序序列:ABCDEFGHK,中序序列:BDCAHGKFE,后序序列:DCBHKGFEA,一.何谓线索二叉树?,指向该线性序列中的“前驱”和“后继”的指针,称作“线索”,与其相应的二叉树,称作“线索二叉树”,包含“线索”的存储结构,称作“线索链表”,ABCDEFGHK,D,C,B,E,对线索链表中结点的约定:,在二叉链表的结点中增加两个标志域,并作如下规定:,若该结点的左子树不空,则Lchild域的指针指向其左子树,且左标志域的值为“指针Link”;否则,Lchild域的指针指向其“前驱”,且左标志的值为“线索Thread”。,若该结点的右子树不空,则rchild域的指针指向其右子树,且右标志域的值为“指针Link”;否则,rchild域的指针指向其“后继”,且右标志的值为“线索Thread”。,如此定义的二叉树的存储结构称作“线索链表”,typedefstructBiThrNodTElemTypedata;structBiThrNode*lchild,*rchild;/左右指针PointerThrLTag,RTag;/左右标志BiThrNode,*BiThrTree;,线索链表的类型描述:,typedefenumLink,ThreadPointerThr;/Link=0:指针,Thread=1:线索,for(p=firstNode(T);p;p=Succ(p)Visit(p);,由于在线索链表中添加了遍历中得到的“前驱”和“后继”的信息,从而简化了遍历的算法。,二.线索链表的遍历算法,例如:对中序线索化链表的遍历算法,中序遍历的第一个结点?,在中序线索化链表中结点的后继?,左子树上处于“最左下”(没有左子树)的结点,若无右子树,则为后继线索所指结点,否则为对其右子树进行中序遍历时访问的第一个结点,voidInOrderTraverse_Thr(BiThrTreeT,void(*Visit)(TElemTypee)p=T-lchild;/p指向根结点while(p!=T)/空树或遍历结束时,p=Twhile(p-LTag=Link)p=p-lchild;/第一个结点Visit(p-data);/访问结点while(p-RTag=Thread/p进至其右子树根,在中序遍历过程中修改结点的左、右指针域,以保存当前访问结点的“前驱”和“后继”信息。遍历过程中,附设指针pre,并始终保持指针pre指向当前访问的、指针p所指结点的前驱。,三.如何建立线索链表?,voidInThreading(BiThrTreep)if(p)/对以p为根的非空二叉树进行线索化InThreading(p-lchild);/左子树线索化if(!p-lchild)/建前驱线索p-LTag=Thread;p-lchild=pre;if(!pre-rchild)/建后继线索pre-RTag=Thread;pre-rchild=p;pre=p;/保持pre指向p的前驱InThreading(p-rchild);/右子树线索化/if,StatusInOrderThreading(BiThrTree/InOrderThreading,if(!T)Thrt-lchild=Thrt;elseThrt-lchild=T;pre=Thrt;InThreading(T);pre-rchild=Thrt;/处理最后一个结点pre-RTag=Thread;Thrt-rchild=pre;,6.6树和森林的表示方法,一.双亲表示法,二.孩子链表表示法,三.树的二叉链表(孩子-兄弟)存储表示法,树的三种存储结构,A,B,C,D,E,F,G,0A-11B02C03D04E25F26G5,r=0n=7,dataparent,一.双亲表示法,typedefstructPTNodeElemdata;intparent;/双亲位置域PTNode;,dataparent,#defineMAX_TREE_SIZE100,结点结构:,C语言的类型描述:,typedefstructPTNodenodesMAX_TREE_SIZE;intr,n;/根结点的位置和结点个数PTree;,树结构:,A,B,C,D,E,F,G,0A-11B02C03D04E25F26G5,r=0n=7,datafirstchild,123,45,6,-1000224,二.孩子链表表示法,typedefstructCTNodeintchild;structCTNode*next;*ChildPtr;,孩子结点结构:,childnext,C语言的类型描述:,typedefstructElemdata;ChildPtrfirstchild;/孩子链的头指针CTBox;,双亲结点结构,datafirstchild,typedefstructCTBoxnodesMAX_TREE_SIZE;intn,r;/结点数和根结点的位置CTree;,树结构:,A,B,C,D,E,F,G,ABCEDFG,root,ABCEDFG,三.树的二叉链表(孩子-兄弟)存储表示法,typedefstructCSNodeElemdata;structCSNode*firstchild,*nextsibling;CSNode,*CSTree;,C语言的类型描述:,结点结构:,firstchilddatanextsibling,设森林F=(T1,T2,Tn);T1=(root,t11,t12,t1m);,二叉树B=(LBT,Node(root),RBT);,森林和二叉树的对应关系,由森林转换成二叉树的转换规则为:,若F=,则B=;否则,由ROOT(T1)对应得到Node(root);由(t11,t12,t1m)对应得到LBT;由(T2,T3,Tn)对应得到RBT。,由二叉树转换为森林的转换规则为:,若B=,则F=;否则,由Node(root)对应得到ROOT(T1);由LBT对应得到(t11,t12,,t1m);由RBT对应得到(T2,T3,Tn)。,由此,树的各种操作均可对应二叉树的操作来完成。,应当注意的是,和树对应的二叉树,其左、右子树的概念已改变为:左是孩子,右是兄弟,6.7树和森林的遍历,一.树的遍历,二.森林的遍历,三.树的遍历的应用,树的遍历可有三条搜索路径:,按层次遍历:,先根(次序)遍历:,后根(次序)遍历:,若树不空,则先访问根结点,然后依次先根遍历各棵子树。,若树不空,则先依次后根遍历各棵子树,然后访问根结点。,若树不空,则自上而下自左至右访问树中每个结点。,ABCDEFGHIJK,先根遍历时顶点的访问次序:,ABEFCDGHIJK,后根遍历时顶点的访问次序:,EFBCIJKHGDA,层次遍历时顶点的访问次序:,ABCDEFGHIJK,BCDEFGHIJK,1.森林中第一棵树的根结点;,2.森林中第一棵树的子树森林;,3.森林中其它树构成的森林。,森林由三部分构成:,1.先序遍历,若森林不空,则访问森林中第一棵树的根结点;先序遍历森林中第一棵树的子树森林;先序遍历森林中(除第一棵树之外)其余树构成的森林。,即:依次从左至右对森林中的每一棵树进行先根遍历。,森林的遍历,中序遍历,若森林不空,则中序遍历森林中第一棵树的子树森林;访问森林中第一棵树的根结点;中序遍历森林中(除第一棵树之外)其余树构成的森林。,即:依次从左至右对森林中的每一棵树进行后根遍历。,先根遍历,后根遍历,树,二叉树,森林,先序遍历,先序遍历,中序遍历,中序遍历,树的遍历和二叉树遍历的对应关系?,设树的存储结构为孩子兄弟链表,typedefstructCSNodeElemdata;structCSNode*firstchild,*nextsibling;CSNode,*CSTree;,一、求树的深度,二、输出树中所有从根到叶子的路径,三、建树的存储结构,intTreeDepth(CSTreeT)if(!T)return0;elseh1=TreeDepth(T-firstchild);h2=TreeDepth(T-nextsibling);/TreeDepth,return(max(h1+1,h2);,一.求树的深度的算法,ABCDEFGHIJK,例如:对左图所示的树,其输出结果应为:,ABEABFACADGHIADGHJADGHK,二.输出树中所有从根到叶子的路径的算法,voidAllPath(BitreeT,Stack/if(T),/输出二叉树上从根到所有叶子结点的路径,voidOutPath(CSTreeT,Stack/while/OutPath,/输出森林中所有从根到叶的路径,和二叉树类似,不同的定义相应有不同的算法。,假设以二元组(F,C)的形式自上而下、自左而右依次输入树的各边,建立树的孩子-兄弟链表。,三.建树的存储结构的算法,A,B,C,D,E,F,G,例如:,对下列所示树的输入序列应为:,(#,A)(A,B)(A,C)(A,D)(C,E)(C,F)(E,G),A,B,C,D,(#,A),(A,B),(A,C),(A,D),(C,E),可见,算法中需要一个队列保存已建好的结点的指针,voidCreateTree(CSTree/所建为根结点else/非根结点的情况/for/CreateTree,GetHead(Q,s);/取队列头元素(指针值)while(s-data!=fa)/查询双亲结点DeQueue(Q,s);GetHead(Q,s);if(!(s-firstchild)s-firstchild=p;r=p;/链接第一个孩子结点elser-nextsibling=p;r=p;/链接其它孩子结点,6.8哈夫曼树与哈夫曼编码,最优树的定义如何构造最优树前缀编码,树的路径长度定义为:树中每个结点的路径长度之和。,结点的路径长度定义为:从根结点到该结点的路径上分支的数目。,一.最优树的定义,树的带权路径长度定义为:树中所有叶子结点的带权路径长度之和WPL(T)=wklk(对所有叶子结点),在所有含n个叶子结点、并带相同权值的m叉树中,必存在一棵其带权路径长度取最小值的树,称为“最优树”。,例如:,2,79,7,5,4,9,2,WPL(T)=72+52+23+43+92=60,WPL(T)=74+94+53+42+21=89,5,4,根据给定的n个权值w1,w2,wn,构造n棵二叉树的集合F=T1,T2,Tn,其中每棵二叉树中均只含一个带权值为wi的根结点,其左、右子树为空树;,(1),(哈夫曼算法)以二叉树为例:,二.如何构造最优树,在F中选取其根结点的权值为最小的两棵二叉树,分别作为左、右子树构造一棵新的二叉树,并置这棵新的二叉树根结点的权值为其左、右子树根结点的权值之和;,(2),从F中删去这两棵树,同时加入刚生成的新树;,重复(2)和(3)两步,直至F中只含一棵树为止。,(3),(4),9,例如:已知权值W=5,6,2,9,7,5,6,2,7,5,2,7,6,9,7,6,7,13,9,5,2,7,6,7,13,9,5,2,7,9,5,2,7,16,6,7,13,29,0,0,0,0,1,1,1,1,00,01,10,110,111,指的是,任何一个字符的编码都不是同一字符集中另一个字符的编码的前缀。,利用哈夫曼树可以构造一种不等长的二进制编码,并且构造所得的哈夫曼编码是一种最优前缀编码,即使所传电文的总长度最短。,三.前缀编码,1.熟练掌握二叉树的结构特性,了解相应的证明方法。2.熟悉二叉树的各种存储结构的特点及适用范围。3.遍历二叉树是二叉树各种操作的基础。实现二叉树遍历的具体算法与所采用的存储结构有关。掌握各种遍历策略的递归算法,灵活运用遍历算法实现二叉树的其它操作。层次遍历是按另一种搜索策略进行的遍历。,本章小结,4.理解二叉树线索化的实质是建立结点与其在相应序列中的前驱或后继之间的直接联系,熟练掌握二叉树的线索化过程以及在中序线索化树上找给定结点的前驱和后继的方法。二叉树的线索化过程是基于对二叉树进行遍历,而线索二叉树上的线索又为相应的遍历提供了方便。,5.熟悉树的各种存储结构及其特点,掌握树和森林与二叉树的转换方法。建立存储结构是进行其它操作的前提,因此读者应掌握1至2种建立二叉树和树的存储结构的方法。6.学会编写实现树的各种操作的算法。7.了解最优树的特性,掌握建立最优树和哈夫曼编码的方法。,
展开阅读全文