1、第五章 树 5.1 树的概念 5.2 二叉树 5.3 二叉树遍历 5.4 二叉树其他运算 5.5 树的存储结构和运算,树形结构是一种非线性结构,应用十分广泛。 如:行政机构、家谱、磁盘目录等,内蒙古大学,理工学院,计算机学院,生命科学学院,外国语学院,人文学院,数学系,物理系,电子系,计算机系,计算中心,网络中心,汉语系,历史系,哲学系,生物系,环境系,动物中心,生物工程中心,资源所,英语系,日语系,行政机构,磁盘目录,树,根-根结点 分枝-分枝结点 叶-叶结点,树的定义:树是n(n=0)结点的有限集,任意非空树: (1) 有且仅有1个特定的结点称为根(Root),无前驱;其余结点有且只有一个
2、前驱,可有m(m=0)个后继. (2) 当n1时,其余的结点可分为m个互不相交的子集 T1, T2, , Tm, 每个又都是树,称为根的子树(Sub tree)在树的定义中用了递归的概念.,树是一种层次结构 (hiberarchy),1,2,3,4,5,5.1树的基本概念,Tree=(D, R) D=Book, C1, C2, C2, S1.1, S1.2, S2.1, S2,2, S2.3, S2.1.1, S2.1.2 R=, , , , , , , , , ,Book,C1,C2,C3,S1.1,S1.2,S2.1,S2.2,S2.3,S2.1.1,S2.1.2,Chapter,Sect
3、ion,Section,树的基本术语(Basic Terminology): 主要来源于家谱和树; 1、双亲、子女(Parent, Child):若a,b属于R,则称a是b的双亲,b是a的子女 2、结点度(Degree):结点的子女数; 3、树的度:最大的结点度; 4、层(Level):根在第1层,其它任一结点所在的层是其双亲的层+1; 5、叶(Leaf):度为0的结点; 6、分枝结点(Branch node)(非终端结点):度不为0的结点; 6、兄弟(Sibling):具有同一双亲的结点互称兄弟; 7、堂兄弟(Cousin):在同层的非兄弟结点互称堂兄弟;,8、深度(Depth):树的最大层
4、数;或称为高; 9、路径(Path):如果有结点序列n1,n2,n3,nk,并且前1个结点是后1个结点的双亲;它的长度是k-1; 10、祖先、后代(ancestor):一个结点是它所有子树中的结点的祖先,这些结点是它的后代; 11、有序树(Ordered tree):每个结点的子女由左到右是有次序的;否则是无序树;,A,B,C,A,C,B,无序,有序,12、森林(Forest):是m棵互不相交的树的集合;,T1,T2,T3,T4,T5,T6,F=T1,T2,Tm, 其中Ti是树。当m=0时,F是空森林。对树中每个结点而言,其子树的集合为森林。反之,若给森林F =T1,T2,Tm中的每棵树的根结
5、点都赋予同一个双亲结点,则构成一棵树。,5.2 二叉树(Binary Tree) 二叉树是另一种树形结构。 5.2.1 二叉树的定义和基本术语 二叉树:是n(n=0)结点的有限集,任意非空树: (1) 有且仅有1个特定的结点称为根(Root); (2) 当n1时,其余的结点最多分为2个互不相交的子集 T1, T2, 每个又都是二叉树,分别称为根左子树和右子树 该逻辑结构起名为:Binary_Tree,二叉树的5种基本形态:,1 空,2 只有根,3 右子树空,4 左子树空,5 左、右子树非空,5.2.1 二叉树的定义和基本术语,DATA BinaryTree isData采用任一种方式存储的一棵
6、二叉树。Operationsvoid InitBTree( /按照一定次序遍历一棵二叉树,5.2.1 二叉树的定义和基本术语,bool FindBTree(/清除二叉树中的所有结点 end BinaryTree,5.2.2 二叉树的性质 二叉树的几个重要性质 性质1: 在二叉树的第i层最多有2i-1 个结点(i=1) 用归纳法证明: 证明:i = 1, 只有根结点,显然成立设i = k时成立,最多有2k-1当i= k+1时,最多的结点个数:2k-1 * 2 = 2k-1+1 = 2k = 2( k+1)-1 性质2: 深度为h的二叉树最多有2h 1个结点; 证明:二叉树的结点个数为:1 + 2
7、 + + 2h-1= 2h-1,性质3: 任一二叉树, 若叶结点树是n0 , 度为2的结点树是n2 ,则: n0 = n2 +1 证明:设n1为T中度为1的结点数,n2为T中度为2的结点数,n0为T中度为0的结点数, 则总结点数:n = n0 + n1 + n2另:除根结点外,所有结点都有且仅有一个双亲结点,也就是仅有一个分支进入,若B为分支数,则B = n1 + 2 * n2 = n - 1;n1 + 2 * n2 = n0 + n1 + n2 1;n0 = n2 + 1;,满二叉树(Full Binary Tree): 每一层的结点树都达到了最大的二叉树.深度为k且有2k-1个结点的二叉树
8、。 编号的满二叉树: 从根开始,由上到下, 从左到右地对每个结点编号的满二叉树. 或者说: 根的编号是1, 一个结点,它的左子女的编号是它的编号的2倍, 它的右子女的编号是它的编号的2倍+1.,编号的满二叉树,完全二叉树 (Complete Binary Tree)若设二叉树共有h层。除第 h 层外,其它各层 (1 h-1) 的结点数都达到最大个数,第 h 层从右向左连续缺若干结点,这就是完全二叉树。,完全二叉树 特征: 1。叶子结点只可能在层次最大的两层上出现 2。任一结点,若其右分支下的子孙的最大层次为l,则其左分支下的最大层次为l或l+1.,性质4: 具有n个结点的完全二叉树,其深度是
9、或,证明:设树的深度为k,则:2k-1 1+1 n 2k 12k-1 n 2k 12k-1 1 n 2k 12k-1 n 2kk-1 log2n kk =,性质4: 具有n个结点的完全二叉树,其深度是 或,证明:设树的深度为k,则:2k-1 1+1 n 2k 12k-1 n 2k 12k-1 1 n 2k 12k-1 n+1 2k k=,性质5:编号的完全二叉树,对任一结点i有: (1) 若i=1, 是根。 若i=1,则它的双亲是 (2)若2i=n,则结点i的左子女是2i; 否则无左子女; (3)若2i+1=n,则结点i的右子女是2i+1; 否则无右子女;,1,2,3,4,6,5,i,i+1,
10、2i,2i+1,注意:度为2的的树(分枝没有左右只分)和二叉树的区别。,5.2.4 二叉树的存储结构 1. 二叉树的顺序存储,考虑 采用顺序存贮结构,实际要求对非线性的结构线性化; 树是一个层次结构 二叉树是有序树 整个二叉树可以按照从上到下,从左到右的顺序排序,做标号; 对于满/完全二叉树,可以从根结点开始按序号存放 对于一般的二叉树,可以参照满二叉树的编码方法进行编码,位置空的结点空置。,按编号顺序依此存储在STn+1中,ST0不用。 根据性质5知:( 1) 若i1,STi 的双亲是STi/2;若i=1,STi为根结点,无双亲。 (2)若2in /2的结点是叶子。 (3)若2i+1=n,则
11、右子女是ST2*i+1,否则,无右子女。 (4)i为奇数且不为1,则STi的左兄弟编号为i-1,否则没有左兄弟 (5) i为偶数且不小于1,则STi的右兄弟编号为i+1,否则没有右兄弟,A B C E H F,0 1 2 3 4 5 6,ST ,完全二叉树的顺序存储:,二叉树的顺序存储:,A B C E F H,0 1 2 3 4 5 6 7 8 9,ST ,根据性质5知: STi 的双亲是STi/2, 左子女是ST2*i 右子女是ST2*i+1 这样太浪费空间。,二叉树的链式存储结构 结点结构,left data right,Left child,Right child,struct BTr
12、eeNodeElemType data;BTreeNode *left,* right; ;,若二叉树为空,root=null 具有n各结点的二叉树,共有2n个指针域,其中n-1个用来指示结点,其余n+1个指针域为空。,二叉树的链式存储结构 结点结构,left data right parent,Left child,Right child,struct BTreeNodeElemType data;BTreeNode * left, * right;BTreeNode * parent; ;,Parent,5.3 二叉树的遍历,N,L,R,遍历次序:,NLR LNR LRN,NRL RNL
13、RLN,前序遍历 中序遍历 后序遍历,中序遍历二叉树算法的框架是: 若二叉树为空,则空操作; 否则 中序遍历左子树 (L); 访问根结点 (V); 中序遍历右子树 (R)。 遍历结果a + b * c - d - e / f,中序遍历 (Inorder Traversal),-,-,/,+,*,a,b,c,d,e,f,二叉树递归的中序遍历算法void InOrder ( BTreeNode * BT ) if ( BT = NULL ) return;elseInOrder ( BT-left );cout data;InOrder ( BT-right ); ,前序遍历二叉树算法的框架是:
14、若二叉树为空,则空操作 否则 访问根结点 (V); 前序遍历左子树 (L); 前序遍历右子树 (R)。 遍历结果- + a * b - c d / e f,前序遍历 (Preorder Traversal),-,-,/,+,*,a,b,c,d,e,f,二叉树递归的前序遍历算法void PreOrder ( BTreeNode * BT ) if ( BT != NULL ) cout data;PreOrder ( BT-left );PreOrder ( BT-right ); ,后序遍历二叉树算法的框架是: 若二叉树为空,则空操作; 否则 后序遍历左子树 (L); 后序遍历右子树 (R);
15、 访问根结点 (V)。遍历结果a b c d - * + e f / -,后序遍历 (Postorder Traversal),-,-,/,+,*,a,b,c,d,e,f,二叉树递归的后序遍历算法void PostOrder ( BTreeNode * BT ) if ( BT != NULL ) PostOrder ( BT-left );PostOrder ( BT-right );cout data; ,#,#,#,#,#,#,#,#,#,中序遍历,我们先观察一下三种遍历行走的路线,*,*,*,*,*,*,*,*,*,前序遍历,&,&,&,&,&,&,&,&,&,后序遍历,&,&,&,&
16、,&,&,&,&,&,*,*,*,*,*,*,*,*,*,#,#,#,#,#,#,#,#,#,三种遍历的访问位置对比:,三种遍历的路线是完全一样的,但访问位置是不同的;,前序:第一次经过时访问,中序:第二次经过时访问,后序:第三次经过时访问,计算二叉树结点个数,int Count (BTreeNode * BT ) if ( BT = NULL ) return 0;else return 1 + Count ( BT-left )+ Count ( BT-right ); ,应用二叉树遍历的事例,统计二叉树的结点数,void InOrder ( BTreeNode *BT,int ,计算二叉
17、树的深度,二叉树的深度 若空树二叉树的深度为0,否则 Depth=max(dep1,dep2)+1 dep1为左子树的深度, dep2为右子树的深度,int Height (BTreeNode * BT ) if ( BT = NULL ) return 0;else if (BT-left=NULL ,求二叉树高度的递归算法,计算二叉树的深度,int Depth ( BTreeNode *BT ) if ( BT = NULL ) return 0;elseint dep1= Depth( BT-left );int dep2=Depth ( BT-right );if(dep1dep2)r
18、eturn dep1+1;else return dep2+1; ,问题: 1、在递归的中序遍历算法中,每个树结点被打印了一次,若另设计一个指针pre跟踪BT,并且同时打印pre的值,如何实现。 2、如何实现非递归的中序遍历算法。,二叉树递归的中序遍历算法void InOrder ( BTreeNode * BT ) if ( BT = NULL ) return;elseInOrder ( BT-left );cout data;InOrder ( BT-right ); ,二叉树递归的中序遍历算法void InOrder ( BTreeNode * BT , BTreeNode * pre
19、 ) if ( BT = NULL ) return;elseInOrder ( BT-left );cout data; pre=BT; InOrder ( BT-right ); ,线索二叉树(Threaded Binary Tree) 目的:利用二叉树的空指针保存遍历序列的前驱、后继信息。 n个结点的二叉树,有2n个指针,只用了n-1个,有n+1个是空的。 用空的左指针指向遍历序列的前驱 用空的右指针指向遍历序列的后继 这两种指针称为线索(Thread)。 为了区分线索与真实指针,给结点增加两个域ltag和rtag,left ltag data rtag right,ltag=0: le
20、ft 指向结点的左子女; ltag=1: left 指向结点前驱; rtag=0: right 指向结点的右子女; rtag=1: right 指向结点后继; 加了线索的二叉树是线索二叉树; 给二叉树加线索的过程是线索化(穿线); 按前序遍历序列穿线的二叉树是前序线索二叉树; 按中序遍历序列穿线的二叉树是中序线索二叉树; 按后序遍历序列穿线的二叉树是后序线索二叉树; 中序线索二叉树简称线索二叉树;,struct TTreeNode ElemType data;bool ltag,rtag;TTreeNode *left, *right; ;,left ltag data rtag right,
21、线索化二叉树 (Threaded Binary Tree),线索 (Thread),中序线索二叉树算法的思想: 若二叉树为空,则空操作; 否则 中序线索左子树 (L); 线索根结点 (HBT); 中序线索右子树 (R)。,中序线索二叉树的算法思想,1、找出 HBT的前驱 pre 2、若pre为HBT的前驱 可以对pre和HBT作线索if(pre!=NULL ,void InThread( TTreeNode * HBT, TTreeNode * ,二叉树递归的中序线索化算法 static TTreeNode *pre=NULL; void InThread( TTreeNode * HBT,
22、TTreeNode * ,中序遍历,中序遍历非递归算法思想: 1、考虑特殊情况,否则从根出发 2、找最左边的结点 3、访问最左边的结点 4、向右走一步 5、若为空则向上走一步转4否则转2,非递归的中序遍历算法,中序遍历非递归算法思想: 1、考虑特殊情况,从根出发 2、找最左边的结点,将经过的结点压栈 3、出栈,访问最左边的结点 4、向右走一步 5、若为空则向上走一步转4否则转2,非递归的中序遍历算法,栈的顺序存储结构struct Stack ElemType *stack; int top; int MaxSize; ; void InitStack(StackType,中序遍历非递归算法进一
23、步细化:void InOrder(TTreeNode *BT) Stack s; InitStack(s); /建立栈 TTreeNode *p=BT; /指向根 if(p=NULL) return ; if(p-left=p-right=NULL)coutdata;return; (2) while ( p != NULL ) Push(s, p); p = p-left; (3) if (EmptyStack(s)=0) /栈非空p=peek(s); Pop(s); /退栈cout data right ; /向右链走 (5) if (p=NULL) goto (3);else goto
24、(4);,中序遍历非递归算法进一步细化:void InOrder(TTreeNode *BT) Stack s; InitStack(s); /建立栈 TTreeNode *p=BT; /指向根 if(p=NULL) return ; if(p-left=p-right=NULL)coutdata;return; while ( p != NULL | EmptyStack(s)=0)(2) while ( p != NULL ) Push(s, p); p = p-left; (3) if (EmptyStack(s)=0) /栈非空p=peek(s); Pop(s); /退栈cout da
25、ta right ; /向右链走,中序遍历非递归算法进一步细化:void InOrder(TTreeNode *BT) Stack s; InitStack(s); /建立栈 TTreeNode *p=BT; /指向根 if(p=NULL) return ; if(p-left=p-right=NULL)coutdata;return; while ( p != NULL | EmptyStack(s)=0)while ( p != NULL ) Push(s, p); p = p-left; if (EmptyStack(s)=0) /栈非空 p=peek(s); Pop(s); /退栈co
26、ut data right ; /向右链走 ,后序遍历:回顾后序遍历,算法思想 后序遍历二叉树 建立栈 stack; P指向根; 当p不空 且 stack 不空时反复做:若 p不空 :(p,L) 入 栈; p向左走一步; 否则: 出栈顶元到p和tag中;若tag=R : 访问p;p置空; 否则 (p,R)入栈;并p向右走一步; 4. 结束,后序遍历时,访问一个结点的时间是: 第3次经过时; 第2次反回时; 从右边返回时; 如何区分从左还是右返回的呢? P入栈时带一标记: 向左走时带L 向左走时带R,建立二叉树 建立是二叉树的各种操作的基础。 建立二叉树的方法很多,我们给出以前序遍历序列建立二叉
27、树的方法,用扩充二叉树的前序遍历序列建立。 是扩充的叶结点,它代表空指针。,D,B,F,E,A,C,该扩充二叉树的前序遍历序列是:ABD*EF*C*,建二叉树算法思想:若二叉树为空树则返回否则1、建根结点2、建左子树3、建右子树 void CreatebiTree (BTreeNode * ,5.5 树的存储结构和运算,一、树的存储struct GTreeNode ElemType data;GTreeNode *tk;struct PGTreeNode ElemType data;PGTreeNode *tk;PGTreeNode *parent;,5.5 树的存储结构和运算 二、树的运算
28、1、森林集与二叉树 之间的转换 森林集与二叉树 集间有一一对应的关系。 (1)森林到二叉树的转换,即构造一个森林对应的二叉树: 设森林 F= T1,T2,Tn, 对于的二叉树是B(F); 算法思想: 若F空,则B(F)空; T1 的根r作为二叉树的根, T1的子树集T11 , T12 , T1m 构成的二叉树作为B(F)的左子树, 其余的树 T2,Tn构成的二叉树作为B(F)的右子树。,例:森林到二叉树的转换,A,B,C,D,E,F,G,H,I,J,例:森林到二叉树的转换,A,B,C,D,E,F,G,H,I,J,A,B,C,D,E,F,G,H,I,J,5.5 树的存储结构和运算 1、森林集与二
29、叉树 之间的转换 (2)二叉树到森林的转换,即构造一个二叉树对应的森林: 设二叉树是B,对于的B的森林 是F(B) 算法思想: 若B空,则F(B)空; B的根r作为T1 的根, B的左子树LT构成的森林T11 , T12 , T1m 做为T1 的子树, B的右子树RT构成的森林做为F(B)的其余的树 T2,Tn。,例:二叉树到森林的转换,A,B,C,D,E,F,G,H,I,J,例:二叉树到森林的转换,A,B,C,D,E,F,G,H,I,J,A,B,C,D,E,F,G,H,I,J,树的二叉树表示,5.5 树的存储结构和运算,2、树的遍历 深度优先遍历先根次序遍历后根次序遍历 广度优先遍历,A,B
30、,C,E,D,A,B,C,D,E,F,G,G,F,(1)树的先根次序遍历,当树非空时访问根结点依次先根遍历根的各棵子树 树先根遍历 ABEFCDG 对应二叉树前序遍历 ABEFCDG 树的先根遍历结果与其对应二叉树表示的前序遍历结果相同,A,B,C,E,D,G,F,5.5 树的存储结构和运算,2、树的遍历,5.5 树的存储结构和运算,树的先根次序遍历 void PreRoot(GTreeNode *GT) if ( GT ! = NULL) coutdatati); ,(2)树的后根次序遍历,当树非空时 依次后根遍历根的各棵子树 访问根结点 树后根遍历 EFBCGDA 对应二叉树中序遍历 EF
31、BCGDA 树的后根遍历结果与其对应二叉树表示的中序遍历结果相同,A,B,C,E,D,G,F,5.5 树的存储结构和运算,2、树的遍历,5.5 树的存储结构和运算,树的后根次序遍历 void PostRoot(GTreeNode *GT) if ( GT ! = NULL) for( int i=0;iti);coutdata ; ,(3)森林的先根次序遍历,当森林非空时访问森林中第一棵树的根结点依次先根遍历第一棵树除去根之后的的各棵子树 依次先根遍历除去第一棵树后的各棵子树,5.5 树的存储结构和运算,2、树的遍历,例:森林到二叉树的转换,A,B,C,D,E,F,G,H,I,J,A,B,C,
32、D,E,F,G,H,I,J,前序序列:ABCDEFGHIJ,前序序列:ABCDEFGHIJ,=,(4)森林的中根次序遍历,当森林非空时 依次先根遍历第一棵树除去根之后的的各棵子树 访问森林中第一棵树的根结点 依次先根遍历除去第一棵树后的各棵子树,5.5 树的存储结构和运算,2、树的遍历,例:森林到二叉树的转换,A,B,C,D,E,F,G,H,I,J,A,B,C,D,E,F,G,H,I,J,前序序列:ABCDEFGHIJ 中序序列:BCDAFEHJIG,前序序列:ABCDEFGHIJ 中序序列:BCDAFEHJIG,=,=,(1)树中的结点数等于所有结点的度数加一。 证明:设树中的总结点数为n
33、, 则树的总枝数应为n-1, 而所有结点的度数等于树的总枝数应 即n-1,故命题成立。 (2)度为k的树中第i层至多有ki-1个结点。 证明:i=1时,命题成立。设度为k的树中第i层有ki-1个结点,则第i+1层上,有ki-1k=ki个结点,命题成立,5.5 树的存储结构和运算,2、树的性质,(3)深度为h的k叉至多有(kh-1) (k-1)个结点 证明:由命题(2)得出:k0+k1+k2+kh-1= (kh-1) (k-1) (4)具有n个结点的k叉树的最小深度为 证明:设树的深度为h,则: (kh-2) (k-1) +1 n (kh-1) (k-1) (kh-2) (k-1) n (kh-1) (k-1)kh-2 n (k-1) kh-1h-2 logk n (k-1) h-1h = logk n (k-1) + 1,5.5 树的存储结构和运算,2、树的性质,上机作业: 1、建一棵树 2、模仿资源管理器建一棵二叉树,并对此二叉树进行前序、中序、后序遍历。要求显示此资源管理器(以树形式). 书面作业 习题5: 5-1,5-2,5-3(1,2,3,4,5),