1、1,第七章 树与森林,教学内容:7.1 树的概念与表示7.2 基本操作与存储7.3 树、森林与二叉树的转换7.4 树或森林的遍历7.5 树的应用 教学目的: 深刻理解树的定义、术语; 领会并掌握树的各种存储结构; 熟练掌握森林与二叉树间的相互转换; 领会树和森林的遍历; 了解树的简单应用。,2,教学重点: 树的存储结构; 森林与二叉树的转换。 教学难点: 森林与二叉树的转换; 判定树; 等价关系与等价类问题。 学时安排: 4学时,3,7.1 树的概念与表示,7.1.1 树的定义及相关术语7.1.2 树的表示,4,7.1.1 树的定义及相关术语,1树的定义树(Tree)是n(n0)个有限数据元素
2、的集合。当n0时,称这棵树为空树。在一棵非树T中:有一个特殊的数据元素称为树的根结点,根结点没有前驱结点。若n1,除根结点之外的其余数据元素被分成m(m0)个互不相交的集合T1,T2,Tm,其中每一个集合Ti(1im)本身又是一棵树。树T1,T2,Tm称为这个根结点的子树。,-递归的概念,5,树的定义还可形式化的描述为二元组的形式:T(D,R) 其中D为树T中结点的集合,R为树中结点之间关系的集合。 当树为空树时,D;当树T不为空树时有:DRootDF 其中,Root为树T的根结点,DF为树T的根Root的子树集合。DF可由下式表示:DFD1D2Dm且DiDj(ij,1im,1jm当树T中结点
3、个数n1时,R;当树T中结点个数n1时有:R,i1,2,m 其中,Root为树T的根结点,ri是树T的根结点Root的子树Ti的根结点。,6,下图是一棵具有9个结点的树,即TA,B,C,H,I,结点A为树T的根结点,除根结点A之外的其余结点分为两个不相交的集合:T1B,D,E,F,H,I和T2=C,G,T1和T2构成了结点A的两棵子树,T1和T2本身也分别是一棵树。例如,子树T1的根结点为B,其余结点又分为两个不相交的集合:T11D,T12E,H,I和T13F。T11、T12和T13构成了子树T1的根结点B的三棵子树。如此可继续向下分为更小的子树,直到每棵子树只有一个根结点为止。,7,从树的定
4、义和图7.1(a)的示例可以看出,树具有下面两个特点:树的根结点没有前驱结点,除根结点之外的所有结点有且只有一个前驱结点。树中所有结点可以有零个或多个后继结点。由此特点可知,下图所示的都不是树结构。,8,2相关术语在二叉树中介绍的有关概念在树中仍然适用。除此之外,再介绍两个关于树的术语。有序树和无序树。如果一棵树中结点的各子树丛左到右是有次序的,即若交换了某结点各子树的相对位置,则构成不同的树,称这棵树为有序树;反之,则称为无序树。森林。零棵或有限棵不相交的树的集合称为森林。自然界中树和森林是不同的概念,但在数据结构中,树和森林只有很小的差别。任何一棵树,删去根结点就变成了森林。,9,7.1.
5、2 树的表示,树的表示方法有四种,各用于不同的目的。1直观表示法树的直观表示法就是以倒着的分支树的形式表示,下图就是一棵树的直观表示。其特点就是对树的逻辑结构的描述非常直观。是数据结构中最常用的树的描述方法。,10,2嵌套集合表示法所谓嵌套集合是指一些集合的集体,对于其中任何两个集合,或者不相交,或者一个包含另一个。用嵌套集合的形式表示树,就是将根结点视为一个大的集合,其若干棵子树构成这个大集合中若干个互不相交的子集,如此嵌套下去,即构成一棵树的嵌套集合表示。下图就是一棵树的嵌套集合表示。,11,3凹入表示法树的凹入表示法如左图所示。4广义表表示法树用广义表表示,就是将根作为 由子树森林组成的
6、表的名字写在表 的左边,这样依次将树表示出来。(A(B(D,E(H,I),F),C(G),12,7.2 树的基本操作与存储,树的基本操作树的存储结构,13,树的基本操作通常有以下几种:Initiate(t)初始化一棵空树t。Root(x)求结点x所在树的根结点。Parent(t,x)求树t中结点x的双亲结点。Child(t,x,i)求树t中结点x的第i个孩子结点。RightSibling(t,x)求树t中结点x的第一个右边兄弟结点。,14,Insert(t,x,i,s)把以s为根结点的树插入到树t中作为结点x的第i棵子树。Delete(t,x,i)在树t中删除结点x的第i棵子树。Tranver
7、se(t)是树的遍历操作,即按某种方式访问树t中的每个结点,且使每个结点只被访问一次。,15,7.2.2 树的存储结构,1双亲表示法由树的定义可以知道,树中的每个结点都有唯一的一个双亲结点,根据这一特性,可用一组连续的存储空间(一维数组)存储树中的各个结点,数组中的一个元素表示树中的一个结点,数组元素为结构体类型,其中包括结点本身的信息以及结点的双亲结点在数组中的序号,树的这种存储方法称为双亲表示法。其存储表示可描述为:#define MAXNODE typedef struct datatype data;int parent;NodeType;NodeType tMAXNODE;,16,下
8、图所示为树的双亲表示。图中用parent域的值为-1表示该结点无双亲结点,即该结点是一个根结点。,17,树的双亲表示法对于实现Parent(t,x)操作和Root(x)操作很方便。但若求某结点的孩子结点,即实现Child(t,x,i)操作时,则需查询整个数组。此外,这种存储方式不能够反映各兄弟结点之间的关系,所以实现RightSibling(t,x)操作也比较困难。在实际中,如果需要实现这些操作,可在结点结构中增设存放第一个孩子的域和存放第一个右兄弟的域,就能较方便地实现上述操作了。,18,2孩子表示法 多重链表法由于树中每个结点都有零个或多个孩子结点,因此,可以令每个结点包括一个结点信息域和
9、多个指针域,每个指针域指向该结点的一个孩子结点,通过各个指针域值反映出树中各结点之间的逻辑关系。在这种表示法中,树中每个结点有多个指针域,形成了多条链表,所以这种方法又常称为多重链表法。在一棵树中,各结点的度数各异,因此结点的指针域个数的设置有两种方法:每个结点指针域的个数等于该结点的度数;每个结点指针域的个数等于树的度数。,19,孩子链表表示法孩子链表法是将树按如下图所示的形式存储。其主体是一个与结点个数一样大小的一维数组,数组的每一个元素有两个域组成,一个域用来存放结点信息,另一个用来存放指针,该指针指向由该结点孩子组成的单链表的首位置。单链表的结构也由两个域组成,一个存放孩子结点在一维数
10、组中的序号,另一个是指针域,指向下一个孩子。,20,在孩子表示法中查找双亲比较困难,查找孩子却十分方便,故适用于对孩子操作多的应用。 这种存储表示可描述为:#define MAXNODE typedef struct ChildNodeint childcode;struct ChildNode *nextchild;typedef struct datatype data;struct ChildNode *firstchild;NodeType;NodeType tMAXNODE;,21,3双亲孩子表示法双亲表示法是将双亲表示法和孩子表示法相结合的结果。其仍将各结点的孩子结点分别组成单链表
11、,同时用一维数组顺序存储树中的各结点,数组元素除了包括结点本身的信息和该结点的孩子结点链表的头指针之外,还增设一个域,存储该结点双亲结点在数组中的序号。下图所示为采用这种方法的存储示意图。,22,4孩子兄弟表示法这是一种常用的存储结构。其方法是这样的:在树中,每个结点除其信息域外,再增加两个分别指向该结点的第一个孩子结点和下一个兄弟结点的指针。在这种存储结构下,树中结点的存储表示可描述为:typedef struct TreeNode datatype data;struct TreeNode *lchild;struct TreeNode *nextsibling;NodeType,*CST
12、ree;,23,下图给出采用孩子兄弟表示法时的存储示意图。,24,从树的孩子兄弟表示法可以看到,如果设定一定规则,就可用二叉树结构表示树和森林,这样,对树的操作实现就可以借助二叉树存储,利用二叉树上的操作来实现。,25,7.3 树、森林与二叉树的转换,树转换为二叉树森林转换为二叉树二叉树转换为树和森林,26,7.3.1 树转换为二叉树,将一棵树转换为二叉树的方法是: 树中所有相邻兄弟之间加一条连线。 对树中的每个结点,只保留它与第一个孩子结点之间的连线,删去它与其它孩子结点之间的连线。 以树的根结点为轴心,将整棵树顺时针转动一定的角度,使之结构层次分明。由上面的转换可以看出:在二叉树中,左分支
13、上的各结点在原来的树中是父子关系,而右分支上的各结点在原来的树中是兄弟关系。由于树的根结点没有兄弟,所以变换后的二叉树的根结点的右孩子必为空。,27,转换为二叉树的转换过程示意图:,28,7.3.2 森林转换为二叉树,由森林的概念可知,森林是若干棵树的集合,只要将森林中各棵树的根视为兄弟,森林同样可以用二叉树表示。森林转换为二叉树的方法如下:将森林中的每棵树转换成相应的二叉树。第一棵二叉树不动,从第二棵二叉树开始,依次把后一棵二叉树的根结点作为前一棵二叉树根结点的右孩子,当所有二叉树连起来后,此时所得到的二叉树就是由森林转换得到的二叉树。,29,这一方法可形式化描述为:如果F T1,T2,Tm
14、 是森林,则可按如下规则转换成一棵二叉树B(root,LB,RB)。 若F为空,即m0,则B为空树;若F非空,即m0,则B的根root即为森林中第一棵树的根Root(T1);B的左子树LB是从T1中根结点的子树森林F1 T11,T12,T1m1 转换而成的二叉树;其右子树RB是从森林F T2,T3,Tm 转换而成的二叉树。,30,下图给出森林及其转换为二叉树的过程:,31,7.3.3 二叉树转换为树和森林,树和森林都可以转换为二叉树,这一转换过程是可逆的,即可以将一棵二叉树还原为树或森林,具体方法如下:若某结点是其双亲的左孩子,则把该结点的右孩子、右孩子的右孩子都与该结点的双亲结点用线连起来;
15、删去原二叉树中所有的双亲结点与右孩子结点的连线;整理由、两步所得到的树或森林,使之结构层次分明。,32,这一方法可形式化描述为:如果B(root,LB,RB)是一棵二叉树,则可按如下规则转换成森林F T1,T2,Tm 。若B为空,则F为空;若B非空,则森林中第一棵树T1的根ROOT(T1)即为B的根root;T1中根结点的子树森林F1是由B的左子树LB转换而成的森林;F中除T1之外其余树组成的森林F T2,T3,Tm 是由B的右子树RB转换而成的森林。,33,下图给出一棵二叉树还原为森林的过程示意。,34,7.4 树和森林的遍历,树的遍历森林的遍历,35,7.4.1 树的遍历,1先根遍历先根遍
16、历的定义为:访问根结点;按照从左到右的顺序先根遍历根结点的每一棵子树。,按照树的先根遍历的定义,对上图所示的树进行先根遍历,得到的结果序列为:A B E F C D G,36,2后根遍历后根遍历的定义为:按照从左到右的顺序后根遍历根结点的每一棵子树。访问根结点;,按照树的后根遍历的定义,对下图所示的树进行后根遍历,得到的结果序列为:E F B C G D A,37,7.4.2 森林的遍历,1前序遍历前序遍历的定义为:访问森林中第一棵树的根结点;前序遍历第一棵树的根结点的子树;前序遍历去掉第一棵树后的子森林。,对于下图所示的森林进行前序遍历,得到的结果序列为:A B C D E F G H J
17、I K,38,2. 后序(中序)遍历后序(中序)遍历的定义为:后序(中序)遍历第一棵树的根结点的子树;访问森林中第一棵树的根结点;后序(中序)遍历去掉第一棵树后的子森林。,对于下图所示的森林进行中序遍历,得到的结果序列为:B A D E F C J H K I G,39,7.5 树的应用,判定树集合的表示关系等价求等价类问题,40,7.5.1 判定树,在实际应用中,树可用于判定问题的描述和解决:例:设有八枚硬币,分别表示为a,b,c,d,e,f,g,h,其中有一枚且仅有一枚硬币是伪造的,假硬币的重量与真硬币的重量不同,可能轻,也可能重。现要求以天平为工具,用最少的比较次数挑选出假硬币,并同时确
18、定这枚硬币的重量比其它真硬币是轻还是重。问题的解决过程如下图所示:,解决过程中的一系列判断构成了树结构,我们称这样的树为判定树。,41,7.5.2 集合的表示,集合是一种常用的数据表示方法,对集合可以作多种操作,假设集合S由若干个元素组成,可以按照某一规则把集合S划分成若干个互不相交的子集合,例如,集合S1,2,3,4,5,6,7,8,9,10,可以被分成如下三个互不相交的子集合:S11,2,4,7S23,5,8S36,9,10集合S1,S2,S3就被称为集合S的一个划分。此外,在集合上还有最常用的一些运算,比如集合的交、并、补、差以及判定一个元素是否是集合中的元素,等等。,42,为了有效地对
19、集合执行各种操作,可以用树结构表示集合-核心问题是在一个集中。用树中的一个结点表示集合中的一个元素,树结构采用双亲表示法存储。例如,集合S1、S2和S3可分别表示为图(a)、(b)、(c)所示的结构。将它们作为集合S的一个划分,存储在一维数组中,如下图所示。,43,数组元素结构的存储表示描述如下:typedef struct datatype data;int parent; NodeType;其中data域存储结点本身的数据,parent域为指向双亲结点的指针,即存储双亲结点在数组中的序号。,44,当集合采用这种存储表示方法时,很容易实现集合的一些基本操作。例7-1:求两个集合的并集。,如求
20、上述集合S1和S2的并集,可以表示为:S1S21,2,3,4,5,7,8,两个集合合并成一个集合,操作上就是简单的把一个集合的树根结点作为另一个集合的树根结点的孩子结点。,45,【算法7-1】 集合并运算算法 int Union(NodeType a,int i,int j)/两个集合的合并/集合以树的双亲表示法存储,且两个集合的根分别在分量i和jif (ai.parent!=-1 | aj.parent!=-1) cout”调用参数不正确”endl;return 0;aj.parent=i; /将i置为两个集合共同的根结点return 1; ,46,例7-2:查找某元素所属的集合。沿着该元素
21、的双亲域向上查,当查到某个元素的双亲域值为1时,该元素就是所查元素所属集合的树根结点。,【算法7-2】 查找元素所在集合算法 int Find(NodeType a,DataType x) /在某组集合a中查找值为x的元素所属的集合 /找到,返回树根结点在数组a中的序号;否则,返回1。 /常量MAXNODE为数组a的最大容量int i,j;i=0;while (i=MAXNODE)return 1; /若x不属该组集合,返回代码1j=i;while (aj.parent!=-1)j=aj.parent;return j; /查找x的结点 ,47,7.5.3 等价类问题,1.问题:已知集合S及其
22、上的等价关系R,求R在S上的一个划分S1,S2,Sn,其中,S1,S2,Sn分别为R的等价类,它们满足: SiS 且 SiSj(ij) 设集合S中有n个元素,关系R中有m个序偶对。,48,2.算法思想:令S中每个元素各自形成一个单元素的子集,记作S1,S2,Sn;重复读入m个序偶对,对每个读入的序偶对,判定x和y所属子集。不失一般性,假设xSi,ySj,若SiSj,则将Si并入Sj,并置Si为空(或将Sj并入Si,并置Sj为空);若SiSj,则不做什么操作,接着读入下一对序偶。直到m个序偶对都被处理过后,S1,S2,Sn中所有非空子集即为S的R等价类,这些等价类的集合即为集合S的一个划分。 3
23、.数据的存储结构:对集合的存储采用双亲表示法来存储本算法中的集合。,49,4.算法实现通过前面的分析可知,本算法在实现过程中所用到的基本操作有以下两个: Find(S,x)查找函数。确定集合S中的单元素x所属子集Si,函数的返回值为该子集树根结点在双亲表示法数组中的序号; Union(S,i,j)集合合并函数。将集合S的两个互不相交的子集合并,i和j分别为两个子集用树表示的根结点在双亲表示法数组中的序号。合并时,将一个子集的根结点的双亲域的值由没有双亲改为指向另一个子集的根结点。,50,这两个操作的实现在7.5.2小节中已经介绍过,下面就本问题的解决算法步骤给出描述: k=1 若km则转,否则
24、转 读入一序偶对 i= Find(S,x); j= Find(S,y) 若ij,则Union(S,i,j); k+输出结果,结束。,51,5.算法的时间复杂性:集合元素的查找算法和不相交集合的合并算法的时间复杂度分别为O(d)和O(1),其中d是树的深度。这种表示集合的树的深度和树的形成过程有关。在极端的情况下,每读入一个序偶对,就需要合并一次,即最多进行m次合并,若假设每次合并都是将含成员多的根结点指向含成员少的根结点,则最后得到的集合树的深度为n,而树的深度与查找有关。这样全部操作的时间复杂性可估计为O(mn)。,52,若将合并算法进行改进,即合并时将含成员少的根结点指向含成员多的根结点,这样会减少树的深度,从而减少了查找时的比较次数,促使整个算法效率的提高。,