《数据结构第六章树课件》由会员分享,可在线阅读,更多相关《数据结构第六章树课件(180页珍藏版)》请在金锄头文库上搜索。
1、6.1 树的类型定义树的类型定义6.2 二叉树二叉树6.3 遍历和线索二叉树遍历和线索二叉树6.4 树和森林树和森林6.5 树与等价问题树与等价问题6.6 哈夫曼树哈夫曼树及其应用及其应用6.1 树的类型定义树的类型定义数据对象数据对象 D:D是具有相同特性的数据元素的集合。是具有相同特性的数据元素的集合。 若若D为空集,则称为空树;为空集,则称为空树; 否则否则: (1) 在在D中存在唯一的称为根的数据元素中存在唯一的称为根的数据元素root, (2) 当当n1时,其余结点可分为时,其余结点可分为m (m0)个互个互 不相交的有限集不相交的有限集T1, T2, , Tm, 其中每一其中每一
2、棵子集本身又是一棵符合本定义的树,棵子集本身又是一棵符合本定义的树, 称为根称为根root的子树。的子树。 数据关系数据关系 R:ABCDEFGHIJMKLA( )T1T3T2树根例如例如: :B(E, F(K, L), C(G), D(H, I, J(M)树具有下面两个特点:树具有下面两个特点:(1 1)树的根结点没有前驱结点,)树的根结点没有前驱结点,除根结点之外的所有结点有且除根结点之外的所有结点有且只有一个前驱结点。只有一个前驱结点。(2 2)树中所有结点可以有零个)树中所有结点可以有零个或多个后继结点。或多个后继结点。() 有确定的根;() 树根和子树根之间为有向关系。有向树:有向树
3、:有序树:有序树:子树之间存在确定的次序关系。无序树:无序树:子树之间不存在确定的次序关系。基基 本本 术术 语语结点结点(node)结点的度结点的度(degree)分支分支(branch)结结点点叶叶(leaf)结点结点孩子孩子(child)结点结点双亲双亲(parent)结结点点兄弟兄弟(sibling)结点结点祖先祖先(ancestor)结点结点子孙子孙(descendant)结结点点结点所处层次结点所处层次(level)树的高度树的高度(depth)树的度树的度(degree)(从根到结点的)路径路径:结点的层次结点的层次: :树的深度:树的深度: 由从根根到该结点所经分支和结点构成A
4、BCDEFGHIJMKL假设根结点的层次为1,第l 层的结点的子树根结点的层次为l+1树中叶子结点所在的最大层次任何一棵非空树是一个二元组 Tree = (root,F)其中:其中:root 被称为根结点, F 被称为子树森林森林:森林:是 m(m0)棵互不相交的树的集合ArootBEFKLCGDHIJMF对比对比树型结构树型结构和和线性结构线性结构的结构特点的结构特点线性结构线性结构树型结构树型结构第一个数据元素第一个数据元素 ( (无前驱无前驱) ) 根结点根结点 ( (无前驱无前驱) )最后一个数据元素最后一个数据元素 (无后继无后继)多个叶子结点多个叶子结点 ( (无后继无后继) )其
5、它数据元素其它数据元素( (一个前驱、一个前驱、 一个后继一个后继) )其它数据元素其它数据元素( (一个前驱、一个前驱、 多个后继多个后继) )6.2 二叉树的类型定义二叉树的类型定义 二叉树或为空树空树;或是由一个根结根结点点加上两棵两棵分别称为左子树左子树和右子树的、互不交的互不交的二叉树二叉树组成。ABCDEFGHK根结点左子树右子树EF二叉树的五种基本形态:二叉树的五种基本形态:N空树空树只含根结点只含根结点NNNLRR右子树为空树右子树为空树L左子树为空树左子树为空树左右子左右子树均不树均不为空树为空树二叉树二叉树的重要特性的重要特性 性质性质 1 : 在二叉树的第 i 层上至多有
6、2i-1 个结点。 (i1)用归纳法用归纳法证明证明: 归纳基归纳基: 归纳假设:归纳假设: 归纳证明:归纳证明:i = 1 层时,只有一个根结点, 2i-1 = 20 = 1;假设对所有的 j,1 j i,命题成立;二叉树上每个结点至多有两棵子树,则第 i 层的结点数 = 2i-2 2 = 2i-1 。性质性质 2 : 深度为 k 的二叉树上至多含 2k-1 个结点(k1)证明:证明: 基于上一条性质,深度为 k 的二叉树上的结点数至多为 20+21+ +2k-1 = 2k-1 性质性质 3 : 对任何一棵二叉树,若它含有n0 个叶子结点、n2 个度为 2 的结点,则必存在关系式:n0 =
7、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 的结点的结点一一对应。123456789 10 11 12 13 14 15abcdefghij 性质性质 4 : 具有 n 个结点的完全二叉树的深度深度为 log2n +1证明:证明:设设 完全二叉树
8、的深度为 k 则根据第二条性质得 2k-1 n 2k 即 k-1 log2 n n,则该结点无左孩子, 否则,编号为 2i 的结点为其左孩子左孩子结点;(3) 若 2i+1n,则该结点无右孩子结点, 否则,编号为2i+1 的结点为其右孩子右孩子结点。6.2.4 二叉树的存储二叉树的存储二、二叉树的链式二、二叉树的链式 存储表示存储表示一、一、 二叉树的顺序二叉树的顺序 存储表示存储表示一、一、 二叉树的顺序存储表示二叉树的顺序存储表示二叉树的顺序存储,就是用一组连续的存储单元存放二叉树中的结点。一般是按照二叉树结点从上至下、从左到右的顺序存储。例如例如: A B D C E F 0 1 2 3
9、 4 5 6 7 8 9 10 11 12 13ABCDEF1401326#define MAXNODE 30/*二叉树的最大结点数*/typedef ELEMTYPE SqBiTreeMAXNODE;/* 0号单元存放根结点 */SqBiTree bt;即将bt定义为含有MAXNODE个elemtype类型元素的一维数组。 完全二叉树和满二叉树适合顺序存储完全二叉树和满二叉树适合顺序存储二、二叉树的链式存储表示二、二叉树的链式存储表示1. 1. 二叉链表二叉链表2三叉链表三叉链表3 3双亲链表双亲链表ADEBCF rootlchild data rchild结点结构结点结构:1. 1. 二叉
10、链表二叉链表typedef struct BiTNode / 结点结构结点结构 ElemType data; struct BiTNode *lchild, *rchild; / 左右孩子指针 BiTNode, *BiTree;lchild data rchild结点结构结点结构:C 语言的类型描述如下语言的类型描述如下: :rootADEBCF 2三叉链表三叉链表parent lchild data rchild结点结构结点结构: typedef struct TriTNode / 结点结构结点结构 TElemType data; struct TriTNode *lchild, *rchi
11、ld; / 左右孩子指针 struct TriTNode *parent; /双亲指针 TriTNode, *TriTree;parent lchild data rchild结点结构结点结构:C 语言的类型描述如下语言的类型描述如下: :结点结构结点结构:3 3双亲链表双亲链表 data parentABDCEF0B41D42C03E14A-15F36LRTagLRRRL根根 typedef struct BPTNode / 结点结构结点结构 TElemType data; int *parent; / 指向双亲的指针 char LRTag; / 左、右孩子标志域 BPTNode typed
12、ef struct BPTree / 树结构树结构 BPTNode nodesMAX_TREE_SIZE; int num_node; / 结点数目 int root; / 根结点的位置 BPTree6.3.1二叉树的遍历二叉树的遍历一、一、问题的提出问题的提出二、二、先左后右的遍历算法先左后右的遍历算法三、三、算法的递归描述算法的递归描述四、四、中序遍历算法的非递归描述中序遍历算法的非递归描述五五、遍历算法的应用举例遍历算法的应用举例 顺着某一条搜索路径巡访巡访二叉树中的结点,使得每个结点均被访问一均被访问一次次,而且仅被访问一次仅被访问一次。一、问题的提出一、问题的提出“访问访问”的含义可
13、以很广,如:输出结点的信息等。 “遍历遍历”是任何类型均有的操作,对线性结构而言,只有一条搜索路径(因为每个结点均只有一个后继),故不需要另加讨论。而二叉树是非线性结构, 每个结点有两个后继每个结点有两个后继,则存在如何遍历存在如何遍历即按什么样的搜索搜索路径路径进行进行遍历的问题。 对对“二二叉叉树树”而而言言,可可以以有有三条搜索路径:三条搜索路径:1先上后下先上后下的按层次遍历;2先先左左(子树)后后右右(子树)的遍历;3先先右右(子树)后后左左(子树)的遍历。abcdefghij二、先左后右的遍历算法二、先左后右的遍历算法先先(根)序的遍历算法中中(根)序的遍历算法后后(根)序的遍历算
14、法根根左子树右子树根根根根根根根根根根 若二叉树为空树,则空操作;否则,(1)访问根结点;(2)先序遍历左子树;(3)先序遍历右子树。先(根)序的遍历算法:先(根)序的遍历算法:ADBCD L RAD L RD L RBDCD L R先序遍历序列:A B D C先序遍历: 若二叉树为空树,则空操作;否则,(1)中序遍历左子树;(2)访问根结点;(3)中序遍历右子树。中(根)序的遍历算法:中(根)序的遍历算法:ADBCL D RBL D RL D RADCL D R中序遍历序列:B D A C中序遍历: 若二叉树为空树,则空操作;否则,(1)后序遍历左子树;(2)后序遍历右子树;(3)访问根结点
15、。后(根)序的遍历算法:后(根)序的遍历算法:ADBC L R DL R DL R DADCL R D后序遍历序列: D B C A后序遍历:B-+/a*b-efcd先序遍历:中序遍历:后序遍历:层次遍历:- + a * b- c d/ e f-+a*b-cd/ef- + a*b- cd/ef-+a*b-c d/e fABCDEFGHK例如:例如:先序先序序列:序列:中序序列:中序序列:后序序列:后序序列:A B C D E F G H KB D C A E H G K FD C B H K G F E A三、算法的递归描述三、算法的递归描述void Preorder (BiTree T, v
16、oid( *visit)(TElemType& e) / 先序遍历二叉树 if (T) visit(T-data); / 访问结点 Preorder(T-lchild, visit); / 遍历左子树 Preorder(T-rchild, visit);/ 遍历右子树 void preorder(JD *bt) if(bt!=NULL) printf(%dt,bt-data); preorder(bt-lchild); preorder(bt-rchild); 主程序主程序Pre( T )返回返回pre(T R);返回返回pre(T R);ACBDTBprintf(B);pre(T L);BT
17、Aprintf(A);pre(T L);ATDprintf(D);pre(T L);DTCprintf(C);pre(T L);C返回T左是空返回pre(T R);T左是空返回T右是空返回T左是空返回T右是空返回pre(T R);先序序列:A B D C中序遍历void inorder(JD *bt) if(bt!=NULL) inorder(bt-lchild); printf(%dt,bt-data); inorder(bt-rchild); 后序遍历void postorder(JD *bt) if(bt!=NULL) postorder(bt-lchild); postorder(bt
18、-rchild); printf(%dt,bt-data); 中序遍历算法的非递归描述中序遍历算法的非递归描述有两种分析(描述)方法:一、“任务书”分析方法二、“路径”分析方法在写算法之前首先需定义栈的元素类型。typedeftypedef enum Travel, Visit TaskType; / Travel = 1:遍历, / Visit = 0:访问 typedeftypedef structstruct BiTree ptr; / 指向根结点的指针 TaskType task; / 任务性质 ElemType;“遍历二叉树遍历二叉树”包括三项子任务:“遍历左子树”“遍历右子树”“访
19、问根结点”voidvoid InOrder_iter( BiTree BT ) / 利用栈实现中序遍历二叉树,T为指向二叉树的根结点的头指针 InitStack(S); e.ptr=BT; e.task=Travel; ifif(T) Push(S, e); / 布置初始任务 whilewhile(! !StackEmpty(S) Pop(S,e); if if (e.task=Visit) visit(e.ptr); else else . . . . . /while /InOrder_iterifif(! !e.ptr) / 处理非空树的遍历任务 p=e.ptr; e.ptr=p-rch
20、ild; Push(S,e); / 最不迫切任务进栈 e.ptr=p; e.task=Visit; Push(S,e); e.ptr=p-lchild; e.task=Travel; Push(S,e); /if void Inorder_I(BiTree T, void (*visit) (TelemType& e) Stack *S; t = GoFarLeft(T, S); / 找到最左下的结点 while(t) visit(t-data); if (t-rchild) else if ( !StackEmpty(S ) t = Pop(S); / 退栈 else t = NULL; /
21、 栈空表明遍历结束 / while/ Inorder_I t = GoFarLeft(t-rchild, S);BiTNode *GoFarLeft(BiTree T, Stack *S) if (!T ) return NULL; while (T-lchild ) Push(S, T); T = T-lchild; return T;中序的非递归算法:void inorder(JD *bt) int i=0; JD *p,*sM; p=bt; do while(p!=NULL) si+=p; p=p-lchild; if(i0) p=s-i; printf(%dt,p-data); p=p
22、-rchild; while(i0|p!=NULL);非递归算法非递归算法ABCDEFGpiP-A(1)ABCDEFGpiP-AP-B(2)ABCDEFGpiP-AP-BP-C(3)p=NULLABCDEFGiP-AP-B访问:访问:C(4)pABCDEFGiP-A访问:访问:C B(5)ABCDEFGiP-AP-D访问:访问:C Bp(6)ABCDEFGiP-AP-DP-E访问:访问:C Bp(7)ABCDEFGiP-AP-D访问:访问:C B Ep(8)ABCDEFGiP-AP-DP-G访问:访问:C B EP=NULL(9)ABCDEFGiP-A访问:访问:C B E G Dp(11)A
23、BCDEFGiP-AP-F访问:访问:C B E G Dp(12)ABCDEFGiP-AP-D访问:访问:C B E Gp(10)ABCDEFGiP-A访问:访问:C B E G D Fp=NULL(13)ABCDEFGi访问:访问:C B E G D F Ap(14)ABCDEFGi访问:访问:C B E G D F Ap=NULL(15)层次遍历二叉树的层次遍历,是指从二叉树的第二叉树的层次遍历,是指从二叉树的第一层(根结点)开始,从上至下逐层遍一层(根结点)开始,从上至下逐层遍历,在同一层中,则按从左到右的顺序历,在同一层中,则按从左到右的顺序对结点逐个访问。对结点逐个访问。按层次遍历所
24、得到的结果序列为:A B C D E F Gabcdefg在进行层次遍历时,可设置一个队列结构,遍历从二叉树的根结点开始,首先将根结点指针入队列,然后从队头取出一个元素,每取一个元素,执行下面两个操作:(1)访问该元素所指结点;)访问该元素所指结点;(2)若该元素所指结点的左、右孩子)若该元素所指结点的左、右孩子结点非空,则将该元素所指结点的左结点非空,则将该元素所指结点的左孩子指针和右孩子指针顺序入队。孩子指针和右孩子指针顺序入队。void LevelOrder(BiTree bt/ 层次遍历二叉树层次遍历二叉树btBiTree queueMAXNODE; int front,rear;if
25、 (bt=NULL) return;Front = -1; rear=0;queuerear=bt;while(front!=rear) front+; Visite(queuefront-data); /*访访问队首结点的数据域问队首结点的数据域*/ if (queuefront-rchild!=NULL) /* 将队首结点的右孩结点入队将队首结点的右孩结点入队 */ rear+; queuerear = queuefront-rchild; if (queuefront-lchild!=NULL) /* 将队首结点的左孩结点入队将队首结点的左孩结点入队 */ rear+; queuerea
26、r = queuefront-lchild; 四四、遍历算法的应用举例遍历算法的应用举例2、统计二叉树中叶子结点的个数、统计二叉树中叶子结点的个数3、求二叉树的深度、求二叉树的深度(后序遍历后序遍历)4、复制二叉树、复制二叉树(后序遍历后序遍历)5 5、建立二叉树的存储结构、建立二叉树的存储结构1、查询二叉树中某个结点、查询二叉树中某个结点1. 在二叉树不空的前提下,和根结点的元素进行比较,若相等,则找到返回 TRUE;2. 否则在左子树中进行查找,若找到,则返回 TRUE;3. 否则继续在右子树中进行查找,若找到,则返回 TRUE,否则返回 FALSE;Status Preorder (Bi
27、Tree T, ElemType x, BiTree &p) / 若二叉树中存在和若二叉树中存在和 x 相同的元素,则相同的元素,则 p p 指向该结点并返回指向该结点并返回 OK,/ 否则返回否则返回 FALSE if (T) if (T-data=x) p = T; return OK, /if else return FALSE;else if (Preorder(T-lchild, x, p) return OK; else return(Preorder(T-rchild, x, p) ;/else2、统计二叉树中叶子结点的个数、统计二叉树中叶子结点的个数算法基本思想算法基本思想:
28、: 先序(或中序或后序)遍历二叉树,在遍历过程中查找叶子结点,并计数。由此,需在遍历算法中增添一个需在遍历算法中增添一个“计数计数”的参数,的参数,并将算法中“访问结点” 的操作改为:若是叶子,则计数器增若是叶子,则计数器增1 1。void CountLeaf (BiTree T, int& count) if ( T ) if (!T-lchild)& (!T-rchild) count+; / 对叶子结点计数 CountLeaf( T-lchild, count); CountLeaf( T-rchild, count); / if / CountLeafint CountLeaf (Bi
29、Tree T)/返回指针T所指二叉树中所有叶子结点个数 if (!T ) return 0; if (!T-lchild & !T-rchild) return 1; else m = CountLeaf( T-lchild); n = CountLeaf( T-rchild); return (m+n); /else / CountLeafint Count (BiTree T)/返回指针T所指二叉树中所有结点个数 if (!T ) return 0; if (!T-lchild & !T-rchild) return 1; else m = Count ( T-lchild); n = C
30、ount ( T-rchild); return (m+n+1); /else / CountLeaf3、求二叉树的深度、求二叉树的深度(后序遍历后序遍历)算法基本思想算法基本思想: : 从二叉树深度的定义可知,二叉树的二叉树的深度应为其左、右子树深度的最大值加深度应为其左、右子树深度的最大值加1 1。由此,需先分别求得左、右子树的深度,需先分别求得左、右子树的深度,算法中“访问结点”的操作为:求得左、求得左、右子树深度的最大值,然后加右子树深度的最大值,然后加 1 1 。 首先分析二叉树的深度二叉树的深度和它的左左、右子右子树深度树深度之间的关系。int Depth (BiTree T )
31、/ 返回二叉树的深度 if ( !T ) depthval = 0; else depthLeft = Depth( T-lchild ); depthRight= Depth( T-rchild ); depthval = 1 + (depthLeft depthRight ? depthLeft : depthRight); return depthval;void Depth(BiTree T , int level, int &dval) if ( T ) if (leveldval) dval = level; Depth( T-lchild, level+1, dval ); De
32、pth( T-rchild, level+1, dval ); / 调用之前 level 的初值为 1。 / dval 的初值为 0.4、复制二叉树、复制二叉树其基本操作为其基本操作为: :生成一个结点。生成一个结点。根元素根元素T左子树左子树右子树右子树根元素根元素NEWT左子树左子树右子树右子树左子树左子树右子树右子树(后序遍历后序遍历)BiTNode *GetTreeNode(TElemType item, BiTNode *lptr , BiTNode *rptr ) if (!(T = new BiTNode) exit(1); T- data = item; T- lchild =
33、 lptr; T- rchild = rptr; return T; 生成一个二叉树的结点生成一个二叉树的结点(其数据域为其数据域为item,左指针域为左指针域为lptr,右指针域为右指针域为rptr)BiTNode *CopyTree(BiTNode *T) if (!T ) return NULL; if (T-lchild ) newlptr = CopyTree(T-lchild); /复制左子树 else newlptr = NULL; if (T-rchild ) newrptr = CopyTree(T-rchild); /复制右子树 else newrptr = NULL; n
34、ewT = GetTreeNode(T-data, newlptr, newrptr); return newT; / CopyTreeABCDEFGHK D C H K G A例如例如: :下列二叉树下列二叉树的复制过程如下的复制过程如下: :newT F B E 5 5、建立二叉树的存储、建立二叉树的存储结构结构不同的定义方法相应有不同的不同的定义方法相应有不同的存储结构的建立算法存储结构的建立算法 以字符串的形式以字符串的形式 “根根 左子树左子树 右子树右子树”定义一棵二叉树定义一棵二叉树例如例如: :以空白字符“ ”表示ABCDA(B( ,C( , ),D( , )空树空树只含一个根
35、结点只含一个根结点的二叉树的二叉树A以字符串“A ”表示以下列字符串表示Status CreateBiTree(BiTree &T) scanf(&ch); if (ch= ) T = NULL; else if (!(T = new BiTNode) exit(OVERFLOW); T-data = ch; / 生成根结点 CreateBiTree(T-lchild); / 构造左子树 CreateBiTree(T-rchild); / 构造右子树 return OK; / CreateBiTreeA B C D A BCD上页算法执行过程举例如下:ATBCDscanf(&ch);if (c
36、h= ) T = NULL;else if (!(T = new BiTNode) exit(OVERFLOW); T-data = ch;CreateBiTree(T-lchild); CreateBiTree(T-rchild); 按给定的表达式建相应二叉树按给定的表达式建相应二叉树 由先缀表示式建树由先缀表示式建树例如:已知表达式的先缀表示式 -+-+ a b c / d e 由原表达式建树由原表达式建树例如:已知表达式 (a+b)c d/e d/e对应先缀表达式 -+-+ a b c / d e的二叉树的二叉树abcde- -+/特点特点: 操作数为叶子叶子结点, 运算符为分支分支结点
37、scanf(&ch);if ( In(ch, 字母集 ) 建叶子结点;else 建根结点; 递归建左子树; 递归建右子树;由先缀表示式建树的算法的基本操作:由先缀表示式建树的算法的基本操作:a+b(a+b)c d/e d/ea+bc 分析表达式和二叉树的关系分析表达式和二叉树的关系:abbac+abc(a+b)c/deabc+- -基本操作基本操作:scanf(&ch);if (In(ch, 字母集 ) 建叶子结点; 暂存; else if (In(ch, 运算符集) 和前一个运算符比较优先数; 若当前的优先数“高”,则暂存; 否则建子树;void CrtExptree(BiTree &T,
38、char exp ) InitStack(S); Push(S, #); InitStack(PTR); p = exp; ch = *p; while (!(GetTop(S)=# & ch=#) if (!IN(ch, OP) CrtNode( t, ch ); / 建叶子结点并入栈 else if ( ch!= # ) p+; ch = *p; / while Pop(PTR, T); / CrtExptree switch (ch) case ( : Push(S, ch); break; case ) : Pop(S, c); while (c!= ( ) CrtSubtree( t
39、, c); / 建二叉树并入栈建二叉树并入栈 Pop(S, c) break; defult : / switch while(!Gettop(S, c) & ( precede(c,ch) CrtSubtree( t, c); Pop(S, c);if ( ch!= # ) Push( S, ch); break;建叶子结点的算法为:void CrtNode(BiTree& T,char ch) if (!(T= new BiTNode) exit(OVERFLOW); T-data = char; T-lchild = T-rchild = NULL; Push( PTR, T );建子树
40、的算法为:void CrtSubtree (Bitree& T, char c) if (!(T= new BiTNode) exit(OVERFLOW); T-data = c; Pop(PTR, rc); T-rchild = rc; Pop(PTR, lc); T-lchild = lc; Push(PTR, T); 仅知二叉树的先序序列“abcdefg” 不能唯一确定一棵二叉树,由二叉树的先序和中序序列建树由二叉树的先序和中序序列建树 如果同时已知二叉树的中序序列“cbdaegf”,则会如何? 二叉树的先序序列二叉树的中序序列左子树左子树左子树左子树 右子树右子树右子树右子树根根根根a
41、 b c d e f gc b d a e g f例如例如: :aab bccddeeffggabcdefg先序序列中序序列void CrtBT(BiTree& T, char pre, char ino, int ps, int is, int n ) / 已知preps.ps+n-1为二叉树的先序序列, / inois.is+n-1为二叉树的中序序列,本算 / 法由此两个序列构造二叉链表 if (n=0) T=NULL; else k=Search(ino, preps); / 在中序序列中查询 if (k= -1) T=NULL; else / / CrtBT if (!(T= new
42、BiTNode) exit(OVERFLOW);T-data = preps;if (k=is) T-Lchild = NULL;else CrtBT(T-Lchild, pre, ino, ps+1, is, k-is );if (k=is+n-1) T-Rchild = NULL;else CrtBT(T-Rchild, pre, ino, ps+1+(k-is), k+1, n-(k-is)-1 ); 6.4 树和森林树和森林 的表示方法的表示方法树的三种存储结构树的三种存储结构一、一、双亲表示法双亲表示法二、二、孩子链表表示法孩子链表表示法三、三、树的二叉链表树的二叉链表( (孩子孩子
43、- -兄弟)兄弟) 存储表示法存储表示法ABCDEFGr=0n=60 A -11 B 02 C 03 D 04 E 2 5 F 26 G 5data parent一、双亲表示法一、双亲表示法: typedef struct PTNode Elem data; int parent; / 双亲位置域 PTNode; data parent#define MAX_TREE_SIZE 100结点结构结点结构:C语言的类型描述语言的类型描述: :typedef struct PTNode nodesMAX_TREE_SIZE; int r, n; / 根结点的位置和结点个数 PTree;树结构树结构:
44、二、孩子链表表示法二、孩子链表表示法: :多重链表:每个结点有多个指针域,分多重链表:每个结点有多个指针域,分别指向其子树的根别指向其子树的根l结点同构:结点的指针个数相等,为结点同构:结点的指针个数相等,为树的度树的度D Dl结点不同构:结点指针个数不等,为结点不同构:结点指针个数不等,为该结点的度该结点的度d ddata child1 child2 . childDdata degree child1 child2 . childdr=0n=6 dataABCDEFG0 A -11 B 02 C 03 D 04 E 25 F 26 G 464 5 1 2 3二、孩子链表表示法二、孩子链表表
45、示法:-1 0 0 0 2 2 4firstchildparenttypedef struct CTNode int child; struct CTNode *nextchild; *ChildPtr;孩子结点结构孩子结点结构: child nextchildC语言的类型描述语言的类型描述: : typedef struct Elem data; ChildPtr firstchild; / 孩子链的头指针 CTBox;双亲结点结构双亲结点结构 data firstchildtypedef struct CTBox nodesMAX_TREE_SIZE; int n, r; / 结点数和根结
46、点的位置 CTree;树结构树结构:ABCDEFGroot AB C E D F G AB C E D F G 三、树的二叉链表三、树的二叉链表 (孩子孩子-兄弟)存储表示法兄弟)存储表示法roottypedef struct CSNode Elem data; struct CSNode *firstchild, *nextsibling; CSNode, *CSTree;C语言的类型描述语言的类型描述: :结点结构结点结构: firstchild data nextsibling将一棵树转换为二叉树的方法是:(1)树中所有相邻兄弟之间加一条连线。(2)对树中的每个结点,只保留它与第一个孩子
47、结点之间的连线,删去它与其它孩子结点之间的连线。(3)以树的根结点为轴心,将整棵树顺时针转动一定的角度,使之结构层次分明。63 二叉树与树、森林之间的转换二叉树与树、森林之间的转换631 树转换为二叉树树转换为二叉树632 森林转换为二叉树 森林转换为二叉树的方法如下:(1)将森林中的每棵树转换成相应的二叉树(2)第一棵二叉树不动,从第二棵二叉树开始,依次把后一棵二叉树的根结点作为前一棵二叉树根结点的右孩子,当所有二叉树连起来后,此时所得到的二叉树就是由森林转换得到的二叉树。633 二叉树转换为树和森林二叉树转换为树和森林(1 1)若某结点是其双亲的左孩子,)若某结点是其双亲的左孩子,则把该结
48、点的右孩子、右孩子的右则把该结点的右孩子、右孩子的右孩子,都与该结点的双亲结点用线孩子,都与该结点的双亲结点用线连起来;连起来;(2 2)删去原二叉树中所有的双亲结)删去原二叉树中所有的双亲结点与右孩子结点的连线;点与右孩子结点的连线; 由此,树和森林的各种操作均可与二叉树的各种操作相对应。 应当注意的是,应当注意的是,和树对应的二叉树,其左、右子树的概念已改变为: 左是孩子,右是兄弟左是孩子,右是兄弟6.4 哈哈 夫夫 曼曼 树树 与与 哈哈 夫夫 曼曼 编编 码码 最优树的定义最优树的定义 如何构造最优树如何构造最优树 前缀编码前缀编码 一、最优树的定义一、最优树的定义树的路径长度树的路径
49、长度定义为: 树中每个结点的路径长度之和。 结点的路径长度结点的路径长度定义为: 从根结点到该结点的路径上 分支的数目。 树的带权路径长度树的带权路径长度定义为: 树中所有叶子结点的带权路径长度结点的带权路径长度之和 WPL(T) = wklk (对所有叶子结点) 在所有含 n 个叶子结点、并带相同权值的 m 叉树中,必存在一棵其带权路径带权路径长度取最小值长度取最小值的树,称为“最优树最优树”。例如:例如:27 9 75492WPL(T)= 72+52+23+43+92 =60WPL(T)= 74+94+53+42+21 =89 54根据给定的 n 个权值 w1, w2, , wn构造 n
50、棵二叉树的集合 F = T1, T2, , Tn,其中每棵二叉树中均只含一个带权值 为 wi 的根结点,其左、右子树为空树;二、如何构造最优树二、如何构造最优树(1)(赫夫曼算法) 以二叉树为例: 在 F 中选取其根结点的权值为最 小的两棵二叉树,分别作为左、 右子树构造一棵新的二叉树,并 置这棵新的二叉树根结点的权值 为其左、右子树根结点的权值之 和;(2) 从F中删去这两棵树,同时加入 刚生成的新树; 重复 (2) 和 (3) 两步,直至 F 中只 含一棵树为止。(3)(4)9例如: 已知权值 W= 5, 6, 2, 9, 7 5627527697671395276713952795271
51、66713292.哈夫曼树的造构造算法哈夫曼树的造构造算法根据二叉树的性质可知,具有n个叶子结点的哈夫曼树共有2n1个结点,所以数组HuffNode的大小设置为2n1weightlchildrchildparent下面给出哈夫曼树的构造算法。#define MAXVALUE 10000 /* 定义最大权值 */#define MAXLEAF 30 /*定义哈夫曼树中叶子结点个数*/#define MAXNODE MAXLEAF*2-1typedef struct int weight; int parent; int lchild,rchild;HNodeType;for (i=0;i2*n-
52、1;i+) /* 数组HuffNode 初始化 */ HuffNodei.weight=0; HuffNodei.parent=-1; HuffNodei.lchild=-1; HuffNodei.rchild=-1; for (i=0;in;i+) scanf(“%d”,&HuffNodei.weight);/* 将找出的两棵子树合并为一棵子树将找出的两棵子树合并为一棵子树 */ HuffNodex1.parent=n+i; HuffNodex2.parent=n+i; HuffNoden+i.weight= HuffNodex1.weight+HuffNodex2.weight; Huff
53、Noden+i.lchild=x1; HuffNoden+i.rchild=x2; for (i=0;in-1;i+) m1=m2=MAXVALUE; x1=x2=0; for (j=0;jn+i;j+) if (HuffNodej.weightm1 & HuffNodej.parent=-1) m2=m1; x2=x1; m1=HuffNodej.weight; x1=j; else if (HuffNodej.weightfirstchild);D2 = Depth(T-nextsibling);Return Maxd1+1,d2int TreeDepth( CTree T ) / T 是
54、树的孩子链表存储结构, / 返回该树的深度 if ( T.n = 0) return 0; else return Depth( T, T.r ); / TreeDepth一、求树的深度的算法:一、求树的深度的算法:int Depth( CTree T, int root ) max = 0; p = T.nodesroot.firstchild; while ( p ) h = Depth( T, p-child ); if ( h max ) max = h; p = p-nextchild; /while return max+1;二、二、输出树中所有从根到叶子的路径的算法输出树中所有从
55、根到叶子的路径的算法: A B C DE F G H I J K例如:对左图所示的树,其输出结果应为:A B EA B FA CA D G H IA D G H JA D G H Kvoid AllPath( BiTree T, Stack& S ) if (T) Push( S, T-data ); if (!T-Lchild & !T-Rchild ) PrintStack(S); else AllPath( T-Lchild, S ); AllPath( T-Rchild, S ); Pop(S); / if(T) / AllPath/ 输出二叉树上从根到所有叶子结点的路输出二叉树上从根
56、到所有叶子结点的路径径void OutPath( Bitree T, Stack& S ) while ( !T ) Push(S, T-data ); if ( !T-firstchild ) Printstack(S); else OutPath( T-firstchild, S ); Pop(S); T = T-nextsibling; / while / OutPath/ 输出森林中所有从根到叶的路径三、建树的存储结构的算法三、建树的存储结构的算法: 和二叉树类似,不同的定义相应有不同的算法。 假设以二元组(F,C)的形式自上而自上而下下、自左而右自左而右依次输入树的各边,建立树的孩子
57、孩子-兄弟链表兄弟链表。ABCDEFG例如例如:对下列所示树的输入序列应为:(#, A)(A, B)(A, C)(A, D)(C, E)(C, F)(E, G)ABCD(#, A)(A, B)(A, C)(A, D)(C, E)可见,算法中需要一个队列队列保存已建好的结点的指针指针void CreatTree( CSTree &T ) T = NULL; for( scanf(&fa, &ch); ch!= ; scanf(&fa, &ch);) p = GetTreeNode(ch); / 创建结点EnQueue(Q, p); / 指针入队列if (fa = ) T = p; / 所建为根结
58、点 else / 非根结点的情况 / for / CreateTree GetHead(Q,s); / 取队列头元素(指针值)while (s-data != fa ) / 查询双亲结点 DeQueue(Q,s); GetHead(Q,s); if (!(s-firstchild) s-firstchild = p; r = p; / 链接第一个孩子结点else r-nextsibling = p; r = p; / 链接其它孩子结点 本章本章作业作业 6.33, 6.34, 6.43, 6.45 6.47, 6.51, 6.56 6.59, 6.61, 6.63, 6.686.5线索二叉树线
59、索二叉树 何谓线索二叉树?何谓线索二叉树? 线索链表的遍历算法线索链表的遍历算法 如何建立线索链表?如何建立线索链表?一、一、何谓线索二叉树?何谓线索二叉树?遍历二叉树的结果是, 求得结点的一个线性序列。ABCDEFGHK例如:先序先序序列: A B C D E F G H K中序中序序列: B D C A H G K F E后序后序序列: D C B H K G F E A指向该线性序列中的“前驱”和 “后继” 的指针指针,称作“线索线索”与其相应的二叉树,称作 “线索二叉树线索二叉树”包含 “线索” 的存储结构,称作 “线索链线索链表表”A B C D E F G H K D C B E
60、对对线索链表线索链表中结点的约定:中结点的约定: 在二叉链表的结点中增加两个标志域增加两个标志域,并作如下规定:若该结点的左子树不空,若该结点的左子树不空,则Lchild域的指针指向其左子树, 且左标志域的值为“指针 Link”; 否则,Lchild域的指针指向其“前驱”, 且左标志的值为“线索 Thread” 。若该结点的右子树不空,若该结点的右子树不空,则rchild域的指针指向其右子树, 且右标志域的值为 “指针 Link”;否则,rchild域的指针指向其“后继”, 且右标志的值为“线索 Thread”。 如此定义的二叉树的存储结构称作如此定义的二叉树的存储结构称作“线索链表线索链表”
61、typedef struct BiThrNod TElemType data; struct BiThrNode *lchild, *rchild; / 左右指针 PointerThr LTag, RTag; / 左右标志 BiThrNode, *BiThrTree;线索链表的类型描述: typedef enum Link, Thread PointerThr; / Link=0:指针,Thread=1:线索二、线索链表的遍历算法二、线索链表的遍历算法: for ( p = firstNode(T); p; p = Succ(p) ) Visit (p);由于在线索链表中添加了遍历中得到的“前
62、驱”和“后继”的信息,从而简化了遍历的算法。例如例如: 对中序线索化链表的遍历算法对中序线索化链表的遍历算法 中序遍历的第一个结点中序遍历的第一个结点 ? 在中序线索化链表中结点的后继在中序线索化链表中结点的后继 ?左子树上处于“最左下最左下”(没有左子树)的结点若若无右子树,则为则为后继线索后继线索所指结点否则为否则为对其右子树右子树进行中序遍历遍历时访问的第一个结点第一个结点void InOrderTraverse_Thr(BiThrTree T, void (*Visit)(TElemType e) p = T-lchild; / p指向根结点 while (p != T) / 空树或遍
63、历结束时,p=T while (p-LTag=Link) p = p-lchild; / 第一个结点 while (p-RTag=Thread & p-rchild!=T) p = p-rchild; Visit(p-data); / 访问后继结点 p = p-rchild; / p进至其右子树根 / InOrderTraverse_Thr 在中序遍历过程中修改结点的在中序遍历过程中修改结点的左、右指针域,以保存当前访问结左、右指针域,以保存当前访问结点的点的“前驱前驱”和和“后继后继”信息。遍历过信息。遍历过程中,附设指针程中,附设指针pre, 并始终保持指并始终保持指针针pre指向当前访问
64、的、指针指向当前访问的、指针p所指所指结点的前驱。结点的前驱。三、如何建立线索链表?三、如何建立线索链表?void InThreading(BiThrTree p) 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-rc
65、hild); / 右子树线索化 / if / InThreadingStatus InOrderThreading(BiThrTree &Thrt, BiThrTree T) / 构建中序线索链表 if (!(Thrt = new BiThrNode) ) exit (OVERFLOW); Thrt-LTag = Link; Thrt-RTag =Thread; Thrt-rchild = Thrt; / 添加头结点 return OK; / InOrderThreading if (!T) Thrt-lchild = Thrt; else Thrt-lchild = T; pre = Thr
66、t; InThreading(T); pre-rchild = Thrt; / 处理最后一个结点 pre-RTag = Thread; Thrt-rchild = pre; 练习题:1. 若一棵二叉树具有10个度为2的结点,则该二叉树的度为0的结点个数是( ) A)9 B)11 C)12 D)不确定2. 有一棵非空的二叉树,(第层为根结点),其第i层上最多有多少个结点?( ) (A) (B) (C) (D)i3.在一棵二叉树中,第5层上的结点数最多为_个,若此树是深度为5的满二叉树,其结点数为_个。4.深度为h 且有_的结点的二叉树称为满二叉树 1.在一棵二叉树中,度为的结点个数为n0,度为的个数为n2,则n0= 。2.该树度为 ,树深度为 。 A / B C D E F G 一棵度为2的树与二叉树有何区别?