1、第5章 树形结构,5.1 树的概念 5.2 二叉树5.2.1 二叉树的概念5.2.2 二叉树的性质5.2.3 二叉树的存储 5.3 二叉树的遍历5.3.1 二叉树的遍历方法5.3.2 二叉树遍历与递归举例 5.4 二叉树的生成 5.5 递归消除5.5.1 简单递归消除5.5.2 基于栈的递归消除 5.6 线索二叉树,非线性结构,可描述数据元素间一对多的逻辑关系,结点之间形成分支和层次关系,类似于自然界中的树。,5.7 树和森林5.7.1 树、林和二叉树的转换5.7.2 树的存储5.7.3 树的遍历 5.8 哈夫曼树及应用5.8.1 最优二叉树5.8.2 哈夫曼编码5.8.3 分类与判定树,5.
2、3 二叉树的遍历,遍历(Traversal ):沿某条搜索路径周游二叉树,对每个结点访问一次且仅访问一次,访问是指对结点进行某种处理,处理的内容依具体问题而定,可以是读、写、修改等,对非线性结构遍历,得到结点按某种次序排列的一个线性序列,所以遍历可看成非线性结构到线性结构的一种映射方法。,5.3.1 二叉树的遍历方法,一、递归遍历 二、层次遍历,A,F,G,E,D,C,B,约定先左后右,则有三种遍历方法:DLR、LDR、LRD ,分别称为先序遍历、中序遍历、后序遍历。,二叉树的遍历可分解为3个子问题:访问根,遍历左子树和遍历右子树,分别用D、L、R表示,则有六种遍历方法: DLR、LDR、LR
3、D;DRL、RDL、RLD。,二叉树由根、左子树、右子树三部分组成,一、 递归遍历,先序遍历(DLR),若二叉树非空 (1)中序遍历左子树; (2)访问根结点; (3)中序遍历右子树。,后序遍历(LRD),先序遍历序列:,A,B,D,E,G,C,F,中序遍历序列:,D,B,G,E,A,C,F,后序遍历序列:,D,G,E,B,F,C,A,若二叉树非空 (1)访问根结点; (2)先序遍历左子树; (3)先序遍历右子树。,中序遍历(LDR),若二叉树非空 (1)后序遍历左子树; (2)后序遍历右子树; (3)访问根结点。,A,F,G,E,D,C,B,void preorder(bitree t) /
4、先根遍历if(t=NULL) return;coutdatalchild); /先根遍历左子树preorder(trchild); /先根遍历右子树 ,void inorder(bitree t) /中根遍历if(t=NULL) return;inorder(tlchild); /中根遍历左子树coutdatarchild); /中根遍历右子树 ,void postorder(bitree t) /后根遍历if(t=NULL) return;postorder(tlchild); /后根遍历左子树postorder(trchild); /后根遍历右子树coutdataendl; /访问根 ,3
5、种遍历的搜索路线相同:从根结点出发,逆时针沿着二叉树外缘移动,每个结点均经过了3次。,若第一次经过时访问,则是前序遍历; 若第二次经过时访问,则是中序遍历; 若第三次经过时访问,则是后序遍历。,中序序列的第一个点是二叉树中最左下的结点;,中序序列的最后一个点是二叉树中最右下的结点。,层次遍历:从第一层(即根)开始,按从上层到下层,每层内从左到右的顺序对结点逐个访问。,层次遍历序列:,A,B,C,D,E,F,G,先访问的结点其孩子结点也先访问,二、层次遍历,每访问一个结点,就将其孩子指针入队,下一个要访问的结点是队头(出队);此过程不断进行,直到队列为空。,用队列保存访问过的结点的孩子。,除第一
6、个结点(即根)外,其它结点都从队列中取出并进行处理。为使根和其它结点的处理一致,可采用预入队技术,即在算法开始时先将根结点入队,然后马上出队再进行有关处理。,入队(根指针); while(队不空) 出队(指针p);访问p;入队(左孩子);入队(右孩子); ,空指针不必入队,A,B,G,C,D,E,F,H,I,Q,root,A,G,H,F,E,D,A,B,C,D,E,F,G,H,I,I,B,C,5.3.2 二叉树遍历与递归举例,对同一个问题可以直接用递归来处理,也可以用某种特殊的遍历来处理。,若问题具有比较明显的逐个结点处理的特点,则用遍历比较直观;否则按根、左子树、右子树三部分递归处理比较简便
7、。,递归出口一般是递归到空或叶子,此时不能再递归,只能回退;递归体一般是对根和左、右子树的某种处理。,例 求二叉树的叶子结点数。,方法1:逐结点处理(遍历):如果当前结点是叶子,则叶子数加1。,int num=0; /num为叶子数,全局量,初始化为0 void leaf(bitree t) if(t=NULL) return; /空树什么都不做if(tlchild=NULL /访问右子树:累计其中的叶子数 ,方法2:递归处理:叶子数等于左子树和右子树的叶子数之和,左子树和右子树本身又是二叉树,求其叶子数递归进行。,int leaf(bitree t) /叶子数通过函数值返回int L,R;i
8、f(t=NULL) return 0;/空树的叶子为0if(tlchild=NULL /叶子数=左右子树叶子数之和(访问根) ,两个递归出口:空和叶子,相当后根遍历:先访问子树(求子树叶子数),再访问根(将左、右子树的叶子数加起来作为当前子树的叶子数)。,5.4 二叉树的生成,基于遍历序列生成:相当于遍历问题的逆问题,即由遍历序列反求二叉树。,3 先序中序,后序中序,单个的遍历序列一般需要补充虚结点才能完整表示结点间的逻辑关系。但两个或多个信息不完整的序列如果能相互补充,也可能得到完整的信息。,(1)对前序序列,序列的第一个点就是整个二叉树的根。 (2)对后序序列,序列的最后一个点就是整个二叉
9、树的根。 (3)对中序序列,以根为界,序列的前一部分在根的左子树中,后一部分在根的右子树中;并且,前一部分构成的子序列是左子树的中序序列,后一部分构成的子序列是右子树的中序序列。,根,若给定前序和中序序列,反复利用上面(1)和(3),就可获得结点完整的逻辑信息,即 由前序序列找到根,由中序序列得到左、右子树; 再对每个子树由前序序列找到子树的根,由中序序列得到子树的左、右子树, 等等类推,每次都可得到一个点(子树的根),从而逐渐分离出树的全部信息,也就可以还原和构造出该二叉树。,由前序和后序遍历序列因不能确定左右子树,一般就不能唯一确定二叉树。,根,例 由先根和中根序列构造二叉树,G,先根序列
10、,中根序列,A,B,H,F,D,E,C,K,G,H,B,D,F,A,E,K,C,A,5.6 线索二叉树,n个结点的二叉链表中含有n+1个空指针域,利用这些空指针域存放某种遍历次序下的前趋和后继指针。这种附加的指针称为“线索”,加上了线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树(Threaded Binary Tree),结点中的指针可能指的是孩子,也可能指的是线索,为了区分, 引入标志位。,中根:DBEFAC,先根:ABDEFC,后根:DFEBCA,5.7 树和森林,树、森林和二叉树的对应关系; 树的存储表示及其遍历。,5.7.1 树、林与二叉树转换,1. 树、森林到二叉树的转换,树
11、转二叉树:,每个结点,它最多只有一个最左边的孩子(长子)和一个右邻的兄弟,将结点的长子变成其左孩子,右兄弟变成其右孩子,森林转二叉树:先将森林中的每一棵树变为二叉树,然后将各二叉树的根结点视为兄弟连在一起。,找结点间1对2的关系:,根结点没有兄弟,所以树转化后二叉树的根结点没有右子树(为空),森林转化后二叉树的根结点有右子树,二叉树转树或森林:左孩子变为长子,右孩子变为右兄弟。,5.7.2 树的存储,一般不能采用顺序存储方式,只能采用链式结构,即除了存储各结点本身的数据外,还要用指针表示结点间的逻辑关系(父子关系),在存储结点信息的同时,为每个结点附设一个指向其双亲的指针。常用静态链表。,1
12、双亲链表,结点在数组中的存储顺序原则上是任意的,但一般将根放在开始位置(否则根需要查找),并且常常按层序编号的顺序存放,找双亲或祖先方便,找孩子或子孙、兄弟不方便(可能要遍历整个数组)。,2 孩子链表,为每个结点建立一个孩子链表;所有孩子链表的头指针用一个数组集中存放,并与存放结点数据的数组合并成一个结构数组(表头数组),找孩子方便,找双亲或祖先则要遍历整个结构不方便。,双亲孩子链表,找孩子、双亲都方便,存储结点信息的同时,附加两个指针域,分别指向该结点最左边的孩子和右邻的兄弟。相当于存储与树对应的二叉树。,3 孩子兄弟链表,长子,右兄弟,firstchild,data,nextsibling
13、,5.7.3 树的遍历,先根遍历:,后根遍历:,层次遍历:,树和森林中,结点可有两棵以上的子树,子树的个数不一定相同,不便讨论中根遍历,但可研究先根和后根遍历。,1,2 先根、后根遍历,(1)前序遍历树T,(2)后序遍历树T,前序序列:,后序序列:,前序遍历树等价于前序遍历该树对应的二叉树; 后序遍历树等价于中序遍历该树对应的二叉树。,若树T非空,则: 访问根结点R。 依次前序遍历根R的各子树T1,T2,Tk。,若树T非空,则: 依次后序遍历根R的各子树T1,T2,Tk。 访问根结点R。,ABEFCDG,EFBCGDA,(1)前序遍历森林,(2)后序遍历森林,若森林非空,则: 访问森林中第一棵
14、树的根结点。 前序遍历第一棵树中根结点的各子树所构成的森林。 前序遍历除第一棵树外其它树构成的森林。,若森林非空,则: 后序遍历森林中第一棵树的根结点的各子树所构成的森林。 访问第一棵树的根结点。 后序遍历除第一棵树外其它树构成的森林。,前序遍历森林就是从左到右依次前序遍历森林中的每一棵树; 后序遍历森林就是从左到右依次后序遍历森林中的每一棵树。,前序序列:,后序序列:,前序遍历森林等价于前序遍历该森林对应的二叉树; 后序遍历森林等价于中序遍历该森林对应的二叉树。,ABECDFGH,EBCDAGHF,3 层序遍历,(1)层序遍历树T,(2)层序遍历森林,层序序列:,若树T非空,则: 访问根结点
15、R。 若第i(i1)层结点已访问,则访问第i+1层结点时,按从左到右的次序依次访问第i+1层上的结点。,将森林中各树的同层结点依次输出。,层序序列:,ABCDE,AFBCDGHE,5.8 哈夫曼树及应用,树的应用非常广,本节以哈夫曼树为例介绍树的应用。,5.8.1 最优二叉树(哈夫曼树),一、定义,结点的权:根据需要给结点赋权于一个有意义的实数; 结点的带权路径长度:结点到根之间的路径长度与该结点权的乘积; 树的带权路径长度(Weighted Path Length ):树中所有叶子结点的带权路径之和;通常记作 WPL= wi Li 哈夫曼树、最优二叉树 :在权为w1, w2, , wn的n个
16、叶结点的所有二叉树中,带权路径长度WPL最小的二叉树。,7 5 2 4,4,7,5,2,4,7,5,2,4,7,5,2,WPL=7*2+5*2+2*2+4*2=36,WPL=7*1+5*2+2*3+4*3=35,WPL=7*3+5*3+2*1+4*2=46,WPL=7*1+5*2+2*3+4*3=35,A,A,A,A,B,B,B,B,C,C,C,C,D,D,D,D,要使WPL小,须将权值大的结点靠近根.,(1)根据n个权值w1, w2, , wn,构造n棵二叉树组成的森林F=T1, T2, , Tn,其中每棵二叉树Ti中只有一个权值为wi的根结点,没有左右子树。 (2)在森林F中选出两棵根结点
17、权值最小的树(若有多个任选两棵),合并成一棵新二叉树。新根结点的权为原两树的根的权值和,原两树作为其左右子树(谁左,谁右无关紧要)。 (3)对新的森林F重复(2),直到只剩一棵树为止。,哈夫曼算法:,权越大的叶子合并的时机越晚,它离最终的根也就越近,哈夫曼树不一定唯一(但WPL相同),合并n1次,每合并一次产生一个新结点,最终结点总数:n+(n1)=2n1,分支结点由合并产生,度为2,所以树中没有度为1的分支结点(称为严格二叉树、正则二叉树),例:以W=(5,15,40,30,10)为权构造哈夫曼树。,15,40,30,5,10,15,30,60,100,编码:数据与二进制字符串的转换。 等长码:各字符的编码长度相等。 总长 ci li,若出现多的编码短,可减少总长度。 前缀(编)码:不等长编码中要求任一字符的编码都不是其它字符编码的前缀。,5.8.2 哈夫曼编码,最优前缀码:平均码长 pi li最小的前缀码。,设:A:0 B:011 C:10 D:110 ABAC 转换成 0011010 译码 AADC0011010 译码 ABAC,不唯一,设字符集D及其概率分布P为: D=d1, d2, , dn P=p1, p2, , pn, pi li表示平均码长,