52第7章树和二叉树7.1知识点分析1.树的定义和术语(1)树树是n(n≥0)个有限数据元素的集合。在任意一棵非空树T中,有且仅有一个特定的称为树根(Root)的结点(根结点无前驱结点),当n1时,除根结点之外的其余结点被分成m(m0)个互不相交的集合T1,T2,…,Tm,其中每一个集合Ti(1≤i≤m)本身又是一棵树,并且称为根的子树。(2)结点树的结点包含一个数据及若干指向其子树的分支。(3)结点的度一个结点所拥有的子树数称为该结点的度。(4)叶子(终端结点)度为零的结点称为终端结点,也称为叶子。(5)树的度树中各结点度的最大值称为该树的度。(6)树的深度树中结点的最大层数称为树的深度或高度。2.二叉树(1)二叉树一棵非空的二叉树,每个结点至多只有两棵子树,分别称为左子树和右子树,左、右子树的次序不能任意交换,且左右子树又分别是一棵二叉树。(2)满二叉树一棵深度为h,且有2h‐1个结点的二叉树称为满二叉树。(3)完全二叉树深度为h,有n个结点的二叉树,当且仅当每一个结点都与深度为h的满二叉树中编号从1至n的结点一一对应时,称此二叉树为完全二叉树。3.关于二叉树的几个最常用的性质性质1一棵非空二叉树的第i层上最多有2i–1个结点(i≥1)。性质2深度为h的二叉树中,最多具有2h-1个结点(h≥1)。性质3对于一棵有n个结点的完全二叉树,若按满二叉树的同样方法对结点进行编号,则对于任意序号为i的结点,有:(1)若i1,则序号为i的结点的父结点的序号为i/2;(2)若2i≤n,则序号为i的结点的左孩子结点的序号为2i;(3)若2i+1≤n,则序号为i的结点的右孩子结点的序号为2i+1。4.遍历二叉树二叉树的遍历是指按某种顺序访问二叉树中的所有结点,使得每个结点都被访问,且仅被访问一次。通过一次遍历,使二叉树中结点的非线性序列转变为线性序列。(1)二叉树前序遍历二叉树前序遍历先访问根结点,再前序遍历左子树,最后前序遍历右子树。(2)二叉树中序遍历二叉树中序遍历先中序遍历左子树,再访问根结点,最后中序遍历右子树。(3)二叉树后序遍历53二叉树后序遍历先后序遍历左子树,再后序遍历右子树,最后访问根结点。(4)层次遍历从根结点开始,按照自上而下,从左到右(同一层)的顺序逐层访问二叉树上的所有结点,这样的遍历称为按层次遍历。5.线索二叉树n个结点的二叉树有n+1个空指针域,可以充分利用二叉链表存储结构中的那些空指针域,来保存结点在某种遍历序列中的直接前驱和直接后继的地址信息。指向直接前驱结点或指向直接后继结点的指针称为线索,带有线索的二叉树称为线索二叉树。对二叉树以某种次序遍历使其变为线索二叉树的过程称为线索化。由于二叉树的遍历方法不同,因此线索二叉树的方法也有多种。其中以中序线索化用得最多。线索二叉树的画法,先写出二叉树的某种遍历的序列,若左孩子为空,则此线索指针将指向前一个遍历次序的结点,右孩子为空,则此线索指针将指向下一个遍历次序的结点,左右不为空时,则不须画。6.恢复二叉树(1)对于已知二叉树的前序和中序序列,可以根据前序序列确定树的根(首结点),根据中序序列确定左子树和右子树。(2)对于已知二叉树的后序和中序序列,可以根据后序序列确定树的根(尾结点),根据中序序列确定左子树和右子树。7.标识符树将算术表达式用二叉树来表示称为标识符树,也称为二叉表示树。利用标识符树的后序遍历可以得到算术表达式的后缀表达式,是二叉树的一种重要应用。8.哈夫曼树及哈夫曼编码(1)路径长度从树中的一个结点到另一个结点之间的分支构成两个结点间的路径,路径上的分支数目,称作路径长度。(2)结点的带权路径长度从该结点到树根之间路径长度与该结点上权的乘积。(3)树的带权路径长度树中所有叶子结点的带权路径长度之和,称为树的带权路径长度。(4)哈夫曼树带权路径长度最小的二叉树,称为最优二叉树,也称为哈夫曼树。(5)哈夫曼编码哈夫曼树从根结点到每个叶结点都有一条唯一的路径,规定哈夫曼树中的左分支代表0,右分支代表1,则从根结点到每个叶结点所经过的路径分支组成的0和1的序列即为该结点对应的哈夫曼编码。7.2典型习题分析【例1】度为2的树与二叉树有什么区别?答:一棵度为2的树与一棵二叉树的区别在于:对于度为1和度为2的树无须区分左右子树,但对于二叉树则必须区分左右子树,且左右子树不能任意交换。【例2】一般树和二叉树有什么区别?答:一般树(非空)除了根结点之外,每个结点有且仅有一个前驱结点,但每个结点都可54以有多个互不相交的子集(后继结点)。二叉树(非空)除了根结点之外,每个结点有且仅有一个前驱结点,但每个结点至多只有两个后继结点,称为左子树和右子树,左、右子树的次序不能交换,且左右子树又分别都是二叉树。一般树和二叉树主要有以下区别:(1)二叉树结点的度最大为2,而一般树结点的最大度数无限制;(2)一般树的结点无左、右之分,而二叉树的结点有左、右之分。【例3】一棵二叉树的先序、中序、后序序列分别如下,其中一部分未给出,填写空格处的内容,并画出二叉树。先序序列:BFICEHG中序序列:DKFIAEJC后序序列:KFBHJGA分析:(1)后序的首结点必等于中序的首结点D,后序的尾结点必等于先序的尾结点G;(2)根据后序确定A为根结点,因此先序的首结点为A,再根据中序划分左、右子树;(3)根据后序确定B为A的左子树的根;(4)根据先序确定C为A的右子树的根;(5)交替使用(1)~(4)可以确定:先序序列:ABDFKICEHJG中序序列:DBKFIAHEJCG后序序列:DKIFBHJEGCA其二叉树如图7-1所示。图7-1恢复二叉树【例4】画出图7-2二叉树对应的森林。图7-2二叉树分析:原二叉树的根结点的右子树肯定是森林,而孩子结点的右子树均为兄弟,画出原二叉树对应的森林如图7-3所示。图7-3二叉树对应的森林DABHCJEKGFIMCABDFKIGEHJ55【例5】高度为h的完全二叉树至少有几个结点?最多有几个结点?分析:设根为第1层,当完全二叉树高度为h时,其前h-1层是高度为h-1层的满二叉树,共有2h-1-1个结点,第h层上至少有1个结点,因此高为h的完全二叉树至少有2h-1-1+1=2h-1个结点。显然高为h的完全二叉树为满二叉树时,结点数最多,结点数为2h-1个。【例6】一棵k个结点的二叉树是左单支树,按一维数组形式顺序存储,需要几个结点的存储空间?分析:左单支树必须扩充为完全二叉树的形式才能使用一维数组进行存储。设根结点的编号为1,沿着左分支往下各结点的编号依次为:2,4,……,2k-1,因此需要2k-1个结点的存储空间。【例7】给定一棵二叉树,输出其嵌套括号表示。分析:显然,选用先序遍历为宜。先输出根结点,再递归遍历二叉树的左子树,再递归编历二叉树的右子树。在输出左子树之前先输出开括号“(”,在输出右子树之后要输出闭括号“)”;若左右子树均为空,则不必输出。输出二叉树的算法:typedefstructBT{datatypedata;BT*lchild;BT*rchild;}BTvoidoutbt(BT*T){if(T!=NULL)printf(“%c”,T-data);if(T-lchild!=NULL||T-rchild!=NULL);{printf(“(”);//只要左、右子树有一个非空,输出开括号outbt(T-lchild)//递归处理二叉树的左子树if(T-lchild!=NULL)printf(“,”);//左、右子树用逗号分割outbt(T-rchild);//递归处理二叉树的右子树printf(“)”);}}【例8】编写一个算法判断两棵二叉树是否等价。分析:设T1,T2分别是两棵二叉树的根指针,所谓等价,有以下几种可能:(1)若T1,T2都为空,则两棵二叉树等价;(2)若一棵二叉树为空,而另一棵不空,则两棵二叉树不相等;(3)若T1,T2都不为空,则分别对相应的子树作判断。算法如下:typedefstructBT{datatypedata;BT*lchild;56BT*rchild;}BTintequal(T1,T2)BTT1,T2;{intY1,Y2;//Y1、Y2用以存放判断结果if(T1==NULL&&T2==NULL)//T1,T2都为空,则两棵二叉树等价printf(“两棵二叉树等价!”);else{Y1=equal(T1-lchild,T2-lchild);//左子树等价,返回值在Y1中Y2=equal(T1-rchild,T2-rchild);//右子树等价,返回值在Y2中if(Y1&&Y2);//左、右子树均等价,则二叉树等价printf(“两棵二叉树等价!”);elseprintf(“两棵二叉树不等价!”);}}【例9】本章单元练习,单选题(18):用5个权值{3,2,4,5,1}构造的哈夫曼树的带权路径长度是()。A.32B.33C.34D.15分析:本题首先构造哈夫曼树如图7-4,然后求带权路径长度。图7-4构造哈夫曼树带权路径长度WPL=(1+2)*3+(3+4+5)*2=33,所以选B。【例10】设有一段正文由字符集{A,B,C,D,E,F}中的字母组成,六个字母在正文中出现的频度分别为:12、18、26、6、4、32。(1)请为这六个字母设计哈夫曼编码。(2)若这段正文开始部分的二进制代码序列为:0110001001011010100,请将它译成对应的正文。分析:在正文中每个字母出现的频度,即叶结点的权值,按权值4、6、12、18、26、32先构造一棵哈夫曼树。为了保证夫曼编码的唯一性,在构造造哈夫曼树时,要求所有结点左子树权值不大于右子树权值。构造哈夫曼树如图7-5所示。633945151257图7-5哈夫曼树和哈夫曼编码将二进制代码序列按哈夫曼编码译成正文的方法:从哈夫曼树的根结点出发,从待译码的二进制码中逐位取码,与二叉树分支上标明的“0”、“1”相匹配,确定一条到达叶子结点的路径。若码是“0”,则沿左分支走,否则沿右分支走到下一层的结点,一旦到达叶子结点,则译出一个字母。然后再从哈夫曼树的根结点出发,从二进制代码的下一位开始继续按上述方法译码,值到所有的编码译完,可以得到如表7-1所示的哈夫曼编码表。对于本题二进制代码序列:0110001001011010100,参照表7-1哈夫曼编码表,可以译成对应的序列为:ABECFDB。表7-1造哈夫曼编码表字母编号对应编码出现频率A01112B0018C1026D01016E01004F1134【例11】根据图7-5二叉树,画出线索二叉树,写出对二叉树进行中序线索化的算法。图7-5二叉树图7-6线索二叉树分析:(1)先写出二叉树的中序遍历序列:DBHEAFCG。08A28DBCDDdDEDFDGDHDNULLNULL08A28DBDCDDDEDFDGDHD16418102640D223460D10000D012D00001111AECDBF58中序线索二叉树如图7-6所示。(2)在线索二叉树中,为了区别每个结点的左、由指针域存放的是孩子的指针还是线索,必须在结点结构中增加两个线索标志域:0lchild域指向结点的左孩子1lchild域指向结点的中序编历次序下的前驱(左线索)0rchild域指向结点的右孩子1rchild域指向结点的中序编历次序下的后继(右线索)其结点结构为:lchildltagtadartagrchild结点类型和相应结点的指针类型定义如下:Typedefstructtnode{chardata;intltag,rtag;//标志ltag,rtag取值只能是0或1structtnode*lchild,*rchild;//左、右子树指针}bt;算法思想:一边中序编历一边建立线索。若访问结点的左孩子为空,则建立前驱线索;若访右孩子为空,则建立后继线索。函数如下:voidthread(p,q)bt*p,*q;//p为当前结点,q为p的前驱结点,开始调用时p为根结点的指针,q为NULL{if(p!=NULL){thr