1.92k likes | 2.04k Views
第六章 树. 6.1 树 6.2 二叉树 6.3 二叉树的线索化 6.4 二叉树、树、森林 6.5 哈夫曼树 6.6 其他树. 6.1 树. 把图 6.1 的文件树抽象化,给予每个结点一个字母,从根结点开始编号,可以得到图 6.3 的树。. 6.1.1 树的定义. 树是由 n(n≥0) 个结点的有限集。 当 n=0 时,称此树为空树。当 n≠0 时,称此树为非空树。在一棵非空树中: ( 1 )有且仅有一个特定的称为根的结点。
E N D
第六章 树 6.1 树 6.2 二叉树 6.3 二叉树的线索化 6.4 二叉树、树、森林 6.5 哈夫曼树 6.6 其他树
6.1 树 把图6.1的文件树抽象化,给予每个结点一个字母,从根结点开始编号,可以得到图6.3的树。
6.1.1 树的定义 树是由n(n≥0)个结点的有限集。 当n=0时,称此树为空树。当n≠0时,称此树为非空树。在一棵非空树中: (1)有且仅有一个特定的称为根的结点。 (2)当n>1时,其余的结点可分为m(m>0)个互不相交的有限集T1,T2,…Tm。其中的每个集合本身又是一棵树,并称为根的子树。
6.1.1 树的定义 例6.1 分析图6.3的树结构。 解:图6.3中的树是一个有16个结点的树,A为根结点,其余的结点分成T1、T2、T3、T4、T5共5棵互不相交的子树。T1={B}、T2={C,G,H,I}、T3={D}、T4={E}、T5={F,J,K,L,M,N,O,P}。。其中T1、T3、T4是只有一个结点的子树;对于T5,F是根结点,其余的结点可以继续划分成T51、T52,T51= {J,L,M,N},T52={ K,O,P },对于T51还可以继续进行类似的划分。由此,发现树的定义是递归的。
6.1.1 树的定义 树是一种数据结构,具有: Tree=(D,R) 其中:D是具有相同性质的数据元素的集合;R是D上的二元关系r的集合,即R={r}。若D中只有一个元素,则R为空集。 对于图6.3, D={A,B,…… ,P},R={<A,B>,<A,C>,<A,D>……,<K,P>} 从上述定义中看出,树具有非线性结构,结点之间的关系是一对多的关系。由于一对多的关系,引出了许多与线性表不同的数据结构和算法。
6.1.2 树的常用术语 树是一种具有复杂关系的数据结构,从树的定义中可以引出如下的常用术语,在后续内容中使用这些概念时将以此为准。下面的说明均以图6.3为例。 度(Degree):结点拥有的子树数叫结点的度。树中各个结点的度的最大值定义为树的度。根结点A的度为5,C的度为3,F的度为2,B的度为0。树A的度为5。 叶子(Leaf):度为0的结点叫终端结点,又称为叶子。 分支结点(Branch node):度不为0的结点叫非终端结点,
6.1.2 树的常用术语 又叫分支结点,度为1的结点叫单分支结点,度为2的结点叫双分支结点,以此类推。如B,D,E,O,P等为叶子结点;F,K为双分支结点;C,J为三分支结点;A为5分支结点。 孩子(Child):结点的子树叫做该结点的孩子;该结点称为此孩子的双亲(Parent);具有相同双亲结点的结点叫兄弟(Sibling);其双亲结点为兄弟的那些结点叫堂兄弟;从根结点开始到达某个结点的所有结点是该结点的祖先(Ancester);某个结点的所有子树上的结点叫该结点的子孙(Descendent)。
6.1.2 树的常用术语 如F结点是J,K结点的双亲;B,C,D是兄弟;I,J和L,O是堂兄弟;A,F,K是O,P的祖先,B~P结点是A的后代。 层次(Level):从根结点开始定义起,根为第一层,根的孩子为第二层。若某结点在第l层,其子树的根就在第l+1层。如A为第一层,B~F处于第二层;G~K处于第三层;L~P处于第四层。 树深(Depth):树中结点的最大层次叫树的深度,又叫做树的高度。图6.3的树深为4。
6.1.2 树的常用术语 有序树和无序树:如果把树中各结点的子树按照从左到右的次序标记,不能互换,则称这样的树为有序树,否则叫无序树。 森林:是n (n≥0) 棵互不相交的树集合。
6.1.3 树的逻辑表示 树的表示有四种形式:树形表示法,文氏图表示法,凹图表示法和广义表表示法。对于图6.3的树,图6.4给出了该树的文氏图(a)、凹图(b)和广义表(c)三种表示法。
6.1.4 树的性质 性质1:树中的结点数等于所有结点的度数和加1。 证明:对于一棵树,除根结点外,每个结点有且只有一个分支连接到该结点的一个孩子,一个结点有几个分支(度)就有几个孩子,所以树的结点数等于所有结点的分支数(度数)之和加上一个根结点,因此,树的结点数等于所有结点的度数和加1。 例6.2,求图6.3所示树的结点数 解:图6.3所有结点的度数D(x)分别为D(A)=5,D(C)=3,
6.1.4 树的性质 D(F)=2,D(J)=3,D(K)=2,其余的结点度数为0,所以该树的分支数之和为5+3+3+2+2=15,显然该树的结点数为16。 性质2:度为m的树中第i层上至多有mi-1个结点(i≥1),即第i层上的结点数n1≤mi-1= m1-1=1。 证明:使用归纳法进行证明,证明如下: 设i=1,树中只有根结点,n1≤mi-1= m1-1=1,结论显然成立; 假设对于第(i-1)层(i>1)命题成立,即ni-1≤mi-2 (度
6.1.4 树的性质 为m的树中第i-1层的结点数至多有mi-2个结点数); 对于第i层,因为树的度数为m,树中每个结点至多有m个孩子,所以第i层的结点数至多是第i-1层的m倍,即mi-2×m= mi-1个,故命题成立。 性质3:高度为h的m叉树至多有(mh-1)/(m-1)个结点。 证明:m叉树意味着树的度为m,根据性质2,第i层上的结点数至多为mi-1(i=1,2…h)。当高度为h的m叉树每一层都为满(即每个结点都有m个子结点,第i层的结点为mi-1个)
6.1.4 树的性质 时,此m叉树拥有的结点数最多,因此m叉树的结点数n至多为: n= m0+ m1+ m2+……+ mh-1=( mh-1)/(m-1) 证毕。 性质4:具有n个结点的m叉树的最小高度为 。 证明:设具有n个结点的m叉树的树高为h,若该树的前第h-1层都是满的(即每一层的结点数都等于mi-1个(i=1,2,h-1)),第h层的结点数可能满,也可能不满,则此树的高度h与结点数n满足如下的关系:
6.1.4 树的性质 (mh-1-1)/(m-1)<n≤( mh-1)/(m-1) (1) 对(1)式进行整理可得: mh-1<n(m-1)+1≤mh 两边以m为底取对数,则有: h-1<logm(n(m-1)+1)≤h (2) 整理(2)式,有 logm(n(m-1)+1)≤h< logm(n(m-1)+1)+1 h只能取整数,所以: h= 证毕。
6.1.4 树的性质 例6.3 计算①高度为4的满3叉树的结点数,②具有20个结点的2叉树的最小高度,③具有20个结点的3叉树的最小高度。 解:① (mh-1)/(m-1)=(34-1)/(3-1)=40 ② = = 5 ③ = = 4
6.1.5 树的存储结构 树的存储结构可以分为顺序存储结构和链表存储结构。这里重点讨论树的链表存储结构。树的链表存储结构分为多重链表和二重链表表示法。除了这种分法,还有双亲存储结构、孩子存储结构和孩子兄弟存储结构。现在分别来讨论这几种存储结构。 1. 多重链表示法 多重链表示法分为定长结点多重链和非定长结点多重链。 (1)定长结点多重链
6.1.5 树的存储结构 取树的度数m作为每个结点的指针域个数。设树的度为3,则结点逻辑结构为: 结点结构定义为: 定义 6.1 typedef char DataType; typedef struct node { DataType data; /*定义某种类型的结点值*/ struct tnode *next[3];/*定义度为3的指针域的个数,对于任意的m,*/ }tnode; /*可以定义成next[m]*/
6.1.5 树的存储结构 这种存储方式,因为以树的度数作为指针域,而树中的多数结点度数不会都是树的度数,所以,很多结点将有大量的空闲指针域,因而造成存储空间浪费。 对于度为m的n个结点的树,这种结构中共有nm个指针域,有n-1条边,所以指针域的使用率为: (n-1)/nm≈1/m 当m越大时,存储空间的利用率越低。因此这种方式适合于存储度数小的树。
6.1.5 树的存储结构 (2)非定长结点多重链 每个结点由数据域、度数域、以及由度数来给定的指针域。因而指针域随着度的不同而变化,这就是非定长多重链表示法。其结点的逻辑结构为: 结点结构定义为: 定义 6.2
6.1.5 树的存储结构 typedef char DataType; typedef struct node { DataType data; /*定义某种类型的结点值*/ int degree; /*定义结点的度*/ struct tnode *next[degree]; /*根据结点的度定义指针域*/ }tnode; 对于图6.5(a)的 树,其存储结构表示 如图6.6。
6.1.5 树的存储结构 2. 二重链表示法 树中每个结点设置三个域:数据域、长子指针域firstC和次弟指针域secendC,其逻辑结构如: 结点结构定义为: 定义 6.3
6.1.5 树的存储结构 typedef char DataType; typedef struct node { DataType data; struct tnode *firstC,*secondC; /*定义指向长子和次子的指针域*/ }tnode; 此时,图6.5(a)的 树表示成图6.7。
6.1.5 树的存储结构 采用这种结构时,图6.5(a)的树可以表示成图6.7。 这种结构实际上与后面讨论的二叉树的结构极为相似,所以树的这种存储结构容易转换成二叉树的存储结构。 3. 双亲存储结构 双亲存储结构使用一个连续的内存空间存储树的所有结点,每个结点用一个附加的指针域来指示其双亲结点的位置。根结点无双亲结点,指针域的值设置成-1,孩子结点的伪指针设置为双亲结点的存储地址。这种结构可以定义如下:
6.1.5 树的存储结构 定义 6.4 typedef char DataType; typedef struct node { DataType data; int father; /*定义结点指向父亲指针*/ }tnode; Tnode t[n]; /*定义一个具有双亲存储结构的数组t*/ int root; /*定义根的父亲指针*/ 这种存储结构首先给根结点编号为0,树中的其他结点从左到
6.1.5 树的存储结构 右从上到下按1、2、3……的顺序编号,图6.8中的第1行就是图6.5(a)中树的结点的编号。这种编号有两个意义,一个是给出下标来确定结点的位置,另一个是为其孩子生成父亲结点索引号,即上面所说的附加指针域。例如,结点A的下标是0,其孩子B、C、D的指向父亲指针域中存放的值是0;结点E的双亲是C,而C的下标是2,故E的指针域中存放的值为2;其余的结点均按照这样的办法来设计其存储结构,因此就有了图6.8中的第3行的值。显然从图6.8很容易找到各个结点的双亲结点。
6.1.5 树的存储结构 右从上到下按1、2、3……的顺序编号,图6.8中的第1行就是图6.5(a)中树的结点的编号。这种编号有两个意义,一个是给出下标来确定结点的位置,另一个是为其孩子生成父亲结点索引号,即上面所说的附加指针域。 例如,结点A的下标是0,其孩子B、C、D的指向父亲指针
6.1.5 树的存储结构 域中存放的值是0;结点E的双亲是C,而C的下标是2,故E的指针域中存放的值为2;其余的结点均按照这样的办法来设计其存储结构,因此就有了图6.8中的第3行的值。显然从图6.8很容易找到各个结点的双亲结点。 这种结构很容易从孩子结点找到其双亲结点,但是从双亲结点寻找孩子结点时出现了问题,因为这种结构中没有孩子结点的地址。
6.1.6 树的基本运算 树是非线性结构,结点间的关系比线性结构要复杂,因此,定义在树结构上的运算比线性结构上的运算要复杂。树的基本运算有三类:查找满足某种特定关系的结点;插入或删除某个结点;遍历树中所有结点并执行某种操作。树的遍历是一种重要的运算,许多其他操作都以它为基础,是本章的重点讨论对象。 树的遍历是指按照某种顺序访问树的每个结点,且只访问一次。根据访问根结点的次序的不同,把遍历分为先(根)序
6.1.6 树的基本运算 遍历,后(根)序遍历两种遍历方式。 1. 先序遍历 先序遍历是一种树的重要操作,它的算法思想是:首先访问根结点,然后访问根结点的孩子,从左至右,依次访问根的所有孩子。这个算法可以递归定义成下面的形式。 算法6.1 先序递归遍历树的算法 preorder(T,x) {visit(x); /*进行访问结点x的操作*/
6.1.6 树的基本运算 for z∈children(x) do /* children(x)为x的所有孩子*/ preorder(T,z); /*对于x的所有孩子,递归调用preorder()函数*/ } 其中,visit(x)表示对结点的访问操作。如果对结点的操作是进行记数,需要把visit(x)设计成一个记数器n,初始值为0,每遍历一个结点进行一次n++操作,当遍历结束时就可以得到树中的结点总数n。如果是输出结点的值,visit(x)函数可以写成printf("%c\t",q->data)。所以,在实际应用中,应该根据具体的访问操作来设计visit函数。
6.1.6 树的基本运算 若执行visit(x)所需的时间为O(1),算法6.1的时间复杂度为T(n)=T(n-1)+O(1)=n×O(1)=n,则访问n个结点所需的时间为O(n)。 根据这个算法,可以画出图6.3的树在先序遍历下的遍历情况(如图6.9)。在先序遍历下,如果对结点的操作是输出结点的值,则得到的元素值序列为ABCGHIDEFJLMNKOP。
6.1.6 树的基本运算 2. 后序遍历 后序遍历也是一种重要的操作,它的思想是:首先访问根结点的孩子,从左至右,依次访问根的所有孩子,最后访问根结点。这个算法可以递归定义成下面的形式。 算法6.2 后序递归遍历树的算法 postorder(T,x) { for z∈children(x) do /* children(x)为x的所有孩子*/ postorder(T,z); /*对于x的所有孩子,递归调用preorder()函数*/ visit(x); /*进行访问结点x的操作*/ }
6.1.6 树的基本运算 若执行visit(x)所需的时间为O(1),算法6.2的时间复杂度的计算与算法6.1类似,也是O(n)。 根据这个算法,可以画出图6.3的树在后序遍历下的遍历情况(如图6.10)。如果访问树的结点的操作是输出结点的值,则后序遍历得到的结点序列为:BGHICDELMNJOPKFA。
6.1.6 树的基本运算 树的后序遍历有广泛的用途,它的一种应用将在6.6.8节中进行讨论。 例6.4 图6.11是一棵磁盘文件系统,图中的数字表示该文件或文件夹的大小,求该磁盘文件树所需的磁盘空间数。
6.1.6 树的基本运算 解:要计算图6.11中/home/user/下的文件树所需的磁盘容量,可以把算法6.2中的visit(x)设计成对每个子文件夹中的文件大小进行求和,最后把求和一直进行到根元素。在每个子文件夹的计算中,先计算子文件夹下各个文件的大小,然后返回到子文件夹,给出该子文件夹下文件的大小;每个子文件夹进行类似的操作;最后返回到根,从而得到该文件树所需要的磁盘空间大小(计算结果标在图6.11中)。这种运算在文件管理中经常使用。
6.1 二叉树 当树的度m为2时,m叉树变成只有两个分支的二叉树。二叉树是一种十分重要的树。具有十分广泛的应用。可以用二叉树进行查找排序、表示算术表达式、解决决策问题等。研究二叉树可以为理解计算机中的某些问题提供解决方案。图6.2中的判断树可以抽象化成图6.12。
6.2.1 二叉树的定义 定义:二叉树是由n(n≥0)个结点的有限集。它或为空树,或为非空树,对于非空树有: (1)有且仅有一个特定的称为根的结点。 (2)除根结点外,其余结点分为互不相交的左子树Tl和右子树Tr,其中,每个子树本身又是一棵树,它们的孩子也构成二叉树,所以二叉树的定义是递归定义。 二叉树是一种数据结构: Binary_Tree=(D,R)
6.2.1 二叉树的定义 其中:D是具有相同性质的数据元素的集合;R是D上二元关系r的集合,若D为空,则R为空集,称此二叉树为空二叉树。 根据这个定义,二叉树有如图6.13的五种基本形态,任何复杂的二叉树形态都可以用这五种基本形态组合起来表示。
6.2.2 二叉树的性质 性质1:二叉树T第i (i≥1)层上的结点数ni至多有2i-1个,即ni≤2i-1。 证明:利用数学归纳法进行证明。 首先,当i=1,只有一个根结点,n1≤2i-1=20=1,命题显然成立。 假设对于第(i-1)层(i>1)命题成立,即ni-1≤2(i-1)-1=2i-2。 对于第i层,由于二叉树的度为2,因此树中每个结点至多有2个孩子,第i层上的结点数最多是第i-1层的2倍,所以:
6.2.2 二叉树的性质 ni-1≤2×ni-1≤×2i-2=2i-1 证毕。 此性质也可以从树的性质2推导得出,读者可以自行推导。 性质2:深度为k(k≥1)的二叉树T结点数n至多有2 k-1个,即n≤2 k-1。 证明:树深为k,则此二叉树共有k层,深度为k的二叉树的结点数就是这k层结点数之和,根据性质1,有:
6.2.2 二叉树的性质 n=n1+n2+……+nk ≤20+21+22+……+2k-1 =2 k-1 证毕。 此性质也可以从树的性质3推导得出,读者不妨自行推导。 性质3:具有n个结点的二叉树T的高度至少为 。 证明: 设树T的高度为h,根据性质2,该树满足:
6.2.2 二叉树的性质 n≤2 h-1 n<2 h 两边以2为底取对数,得 h>log2n log2n为实数,h为整数,因此有。 证毕。 性质4:在任意二叉树T中,若叶子结点个数为n0,度为1的结点数为n1,度为2的结点数为n2,那么n0= n2+1。
6.2.2 二叉树的性质 证明: 设:n为T的结点总数,n0,n1,n2分别为叶子结点数,1分支结点数,2分支结点数。则 n= n0+ n1+ n2 (1) 设B为二叉树的总分支数,除根结点外,每个结点都有一个分支进入,则 B=n-1 (2) 这些分支由度数为1和度数为2的结点发出,所以有:
6.2.2 二叉树的性质 B= n1+ 2n2 (3) 从(1)、(2)、(3)可得:n0= n2+1 证毕。
6.2.2 二叉树的性质 几个重要概念:深度为k并且包含2k-1个结点的二叉树叫满二叉树,如图6.14(a)。对满二叉树的结点进行编号,从根结点开始自上而下,从左到右编号,如图6.14(a)中的数字。深度为k,含有n(n<2k-1)个结点,且每个结点的编号与满二叉树中1~n个结点的编号对应的树叫完全二叉树,如图6.14(b)。不满足此条件的二叉树叫非完全二叉树,如图6.14(c)(d)。 性质5:具有n个结点的完全二叉树T树深为。 证明: