1 / 82

第五章 二叉树及应用

第五章 二叉树及应用. 一种重要的非线性结构. 学习要点:. 二叉树的递归概念,这与二叉树各种基本运算具有密切关联。 满二叉树和完全二叉树概念,二叉树和完全二叉树基本性质。 二叉树的顺序存储与二叉链表存储结构。 二叉树遍历的基本思想和基于递归与非递归实现算法。 线索二叉树概念,二叉树的线索化和遍历。 Huffman 树概念与基本算法; Huffman 编码和实现算法。. §5.1 二叉树及其基本性质. 5.1.1 二叉树基本概念 “二叉树”是一个满足下述条件的由结点组成的有限集合 E : ① 当 E 为空集时,定义其为空二叉树;

dewey
Télécharger la présentation

第五章 二叉树及应用

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 第五章 二叉树及应用 一种重要的非线性结构

  2. 学习要点: • 二叉树的递归概念,这与二叉树各种基本运算具有密切关联。 • 满二叉树和完全二叉树概念,二叉树和完全二叉树基本性质。 • 二叉树的顺序存储与二叉链表存储结构。 • 二叉树遍历的基本思想和基于递归与非递归实现算法。 • 线索二叉树概念,二叉树的线索化和遍历。 • Huffman树概念与基本算法;Huffman编码和实现算法。

  3. §5.1 二叉树及其基本性质 5.1.1 二叉树基本概念 “二叉树”是一个满足下述条件的由结点组成的有限集合E: ① 当E为空集时,定义其为空二叉树; ② 当E非空时,分为两种情形。 ● 如果E为单元素集合,定义其为一棵根二叉树; ● 如果E为多于一个结点的集合,则E中应当具有唯一一个结点r称其为根结点,而集合E’=E \{r}也是一棵二叉树,称为r的子二叉树。此时,结点r至多只能有两棵不相交的子二叉树,并且相应子二叉树有左右之分,分别称为r的左子树和右子树。

  4. 其它一些相关概念: 结点的度:结点拥有的子树棵数 结点的深度(层次):根结点看做第0层,其余结点的层次值为其父结点所在层值加1 树的度:树中所有结点度的最大值 树的深度:树中最大层次数 根结点、叶结点、内部结点 子结点、父结点、兄弟结点、堂兄弟结点、 子孙结点、祖先结点; 4

  5. 5.1.1 二叉树基本概念2 1、二叉树的特征 • 二叉树可以没有任何结点,即是一个空二叉树。 • 二叉树中每个结点至多只有两棵子树,而这两棵子树作为结点集合互不相交; • 二叉树中结点的两棵子树有左、右之分,次序不能颠倒。 2、二叉树基本类型

  6. 5.1.2 满二叉树和完全二叉树 1、满二叉树 如果一棵二叉树满足下述条件,就称其为满二叉树(full binary tree): ① 每个结点或是度数为2(具有两个非空子树)的结点,或是度数为0的(叶)结点; ② 所有叶结点都在同一层。

  7. 5.1.2 满二叉树和完全二叉树2 2、完全二叉树 若一棵二叉树满足下述条件,就称其为完全二叉树(complete binary tree): ① 至多只有最下两层中结点的度数小于2; ② 最下一层的叶结点都依次排列在该层最左边位置。

  8. 5.1.2 满二叉树和完全二叉树2 2、完全二叉树2 重点理解: • 满二叉树是完全二叉树,但完全二叉树却不一定是满二叉树。 • 空二叉树和根二叉树既是满二叉树,也是完全二叉树。 • 完全二叉树可以看作是在满二叉树的最下一层,从右向左连续去掉若干个结点后得到二叉树。 • 完全二叉树中的一个结点如果没有左子结点,就一定没有右子结点。

  9. 练习: × √ • 1、完全二叉树某结点若无右子树则定无左子树。 • 2、完全二叉树某结点若无左子树则定无右子树。

  10. 5.1.3 二叉树基本性质 2i 性质5-1 一棵二叉树第i(i≥0)层上至多只能有个结点。 证明:应用数学归纳法。 二叉树第0层有一个结点,即当i=0时,2i=20=1成立。 假设结论对第i层成立,即第i层至多只能有2i个结点。注意到二叉树每个结点的度最多为2,第i+1层结点个数至多只能是第i层结点个数的2倍,即2*2i = 2i+1,归纳完成,命题得证。

  11. 5.1.3 二叉树基本性质2 性质5-2 树高为k(k≥0)的二叉树,最多有个结点。 2k+1-1 证明: 由性质5-1可知在树高为k的二叉树当中,第0层有20个结点,第1层有21个结点,第2层有22个结点,……,第k层有2k个结点。由此可知,树高为k的二叉树结点个数总数为20 + 21 + 22 +…+ 2k。这是一个公比为p=2的等比数列,前k+1项和Sk+1为: Sk+1 =(a0– akp)/(1−p)= (20−2k2)/(1−2) = (1−2k+1)/(1−2) = 2k+1−1

  12. 5.1.3 二叉树基本性质3 n0=n2+1 性质5-3 如果二叉树中度为0结点数为n0,度为2结点数为n2, 则成立。 证明:设结点总数 n = n0+ n1+ n2 又因为:结点数n = 边数+1 边数 = n1+ 2*n2 即n0 + n1 + n2 = n1 + 2n2 + 1 所以:n0 = n2 + 1 结点数n = 边数+1

  13. 练习: 一棵树的度为4,n4=2, n3=3, n2=4,求n0? 解: 结点数 = 边数 + 1 n0+ n1+n2 +n3+n4 = n1+ 2*n2 + 3*n3 + 4*n4 + 1 n0 + 2 + 3 + 4 = 8 + 9 + 8 + 1 n0=17

  14. 练习: 设完全二叉树有1000个结点,求叶子结点个数?有多少个度为1的结点?度为2的结点呢? n0 = 500 n1 = 1 n2 =499 解:设二叉树中叶子结点、度为1、度为2的结点数目 分别n0、 n1、n2, 其中完全二叉树中n1只能为1或0,则: n0+ n1+ n2 = 1000 n0 = n2+ 1 n1= 0 或n1=1 14

  15. 复习两个概念: 2k+1-1 (1)满二叉树:深度为k的满二叉树有个结点。 对满二叉树按层次从上到下,从左到右,不留一个空位进行编号,号码1~n。 (2)完全二叉树:结点数为n的完全二叉树上各个结点与同深度的满二叉树前n个相应位置结点编号一一对应。

  16. 性质5-4 设BT为具n个结点的完全二叉树,将BT所有结点按照从上到下、从左到右的顺序(二叉树的层序)进行编号。则BT中任意结点N的序号i(1≤i≤n)具有下述性质。 (1)若i=1,则N为BT根结点,N无父结点; (2)若i>1,则N父结点序号为 (即i除以2后向下取整); (3)若2i>n,则N无左子树,否则其左子结点(即左子树的根结点)序号为2i; (4)若2i+1>n,则N无右子树,否则其右子结点(即右子树的根结点)序号为2i+1。 5.1.3 二叉树基本性质4

  17. 练习: 500 int(log2n) 1、1000个结点的完全二叉树最大的分支结点编号为。 2、n个结点的完全二叉树深度为。

  18. §5.2 二叉树存储 5.2.1 二叉树顺序存储 预留最大空间 深度为k的二叉树预留2k+1-1个存储单元,按编号顺序存储,遇空结点留空位。

  19. 5.2.1 二叉树顺序存储2 • 适合满(完全)二叉树,求双亲、孩子方便 • 不适合深度较大、结点不多的二叉树

  20. 5.2.2 二叉树链式存储 1、二叉链表存储 让一个存储结点只包含与其子树的邻接关系,那么就是二叉树的二叉链表存储结构。

  21. 5.2.2 二叉树链式存储 1、二叉链表存储2 用C语言定义二叉链表的结构类型如下: struct node {DataType data; /* 定义数据域,DataType代表实际需要的类型 */ struct node *lch; /* 定义左孩子域,指向左孩子地址 */ struct node *rch; /* 定义右孩子域,指向右孩子地址 */ }; typedef struct node bitree; /* 定义二叉树结点类型为bitree */

  22. 1、二叉链表存储3 算法5-1 创建一棵只有根结点的二叉树算法。 创建只有以x为根结点的二叉树Bt,x的数据类型为DataType,相应结点的Lchild和Rchild域均取值NULL,返回指向根结点的指针。 00 Create_Bt(DataType x) 01 { 02 bitree *Bt,*ptr; 03 ptr = (bitree *) malloc (sizeof(bitree)); /* 申请存储结点 */ 04 Bt=ptr; 05 ptr->data = x; 06 ptr->lch = NULL; 07 ptr->rch = NULL; 08 return (Bt); 09 }

  23. 1、二叉链表存储4 算法5-2 在指定左子结点处插入一个新结点。 已知二叉链表Bt,在指针Parent所指结点左子结点处插入一个数据元素值为x的新结点,使之成为Parnet所指结点新的左子树根结点。 bitree *Inl_Bt(bitree *Bt, bitree *Parent, DataType x) {if (Parent == NULL) { printf ("位置错!"); return (NULL); } ptr = (bitree *) malloc (sizeof(bitree)); /* 申请存储结点空间 */ ptr->data = x; ptr->lch = NULL; ptr->rch = NULL; if (Parent->lch == NULL) /* Parent所指结点左子树为空 */ Parent->lch = ptr; else /* Parent所指结点左子树非空 */ { ptr->lch = Parent->lch; Parent->lch = ptr; } return(Bt) }

  24. 5.2.2 二叉树链式存储2 2、三叉链表存储 同时反映当前结点与其左子树的根结点、右子树的根结点和与其父结点关联。

  25. 5.2.2 二叉树链式存储2 2、三叉链表存储2

  26. §5.3 二叉树的遍历 按照某种确定方式对二叉树进行访问,但要求二叉树中每个结点被访问一次且只被访问一次。 1、先序、中序和后序遍历 对左、右子树,限定“先访左后访右”,那么访问结点的顺序有三种不同的组合形式:TLR、LTR、LRT。 通常,称TLR为二叉树的先序(先根)遍历, LTR为中序(中根)遍历, LRT为后序(后根)遍历。

  27. 例子: 以三种遍历方式访问如图所示的二叉树。 解:先序遍历序列 A-B-D-H-E-C-F-I-G-J-K 中序遍历序列 D-H-B-E-A-I-F-C-J-G-K 后序遍历序列 H-D-E-B-I-F-J-K-G-C-A

  28. 例子: 已知二叉树先序遍历序列是A-B-C-D-E-F-G,中序遍历序列是C-B-D-A-E-G-F。由这两个序列可唯一确定一棵二叉树。 解:从先序遍历序列第一个结点可知二叉树根结点是A。由结点A在中序遍历序列里位置可知该根结点左子树包含结点C-B-D,右子树包含结点E-G-F,如图5-22所示。由中序序列片段C-B-D可知,B是A左子树根结点,再结合先序序列片段B-C-D可知,C和D分别是B的左右子结点。由先序序列片段E-F-G可知,E是A的右子结点,再由先序序列片段F-G和中序序列片段G-F可知,F不能是E的左子结点,故只能是E的右子结点,并且G是F的左子结点。

  29. 练习: • 1、已知二叉树先序遍历序列为ABCDEFGH,中序遍历序列为CDBAFEHG,试画出此二叉树。 • 2、已知二叉树后序遍历序列为DCBFHGEA,中序遍历序列为CDBAFEHG,求先序遍历序列。

  30. §5.3 二叉树的遍历2 2、基于递归遍历算法 递归步骤(先序遍历): ① 访问根结点; ② 先序遍历访问左子二叉树; ③ 先序遍历访问右子二叉树。

  31. 2、基于递归遍历算法2 算法5-4 二叉树先序遍历递归算法。 已知二叉树Bt,对其进行先序遍历,若二叉树为空,则为空操作;否则进行如下操作:访问二叉树根结点;先序遍历二叉树的左子树;先序遍历二叉树的右子树。 00 Pret_Bt(bitree *Bt) 01 { 02 if (Bt != NULL) 03 { 04 printf ("%c", Bt->data); /* 访问根结点 */ 05 Pret_Bt(Bt->lch); /* 先序遍历左子树 */ 06 Pret_Bt(Bt->rch); /* 先序遍历右子树 */ 07 } 08 }

  32. 2、基于递归遍历算法2 基于递归调用先序遍历:

  33. 2、基于递归遍历算法2 先序递归算法应用实例:先序建立二叉树 bitree *creat() { bitree *t; int x; scanf(“%d”,&x); if (x==0) t=NULL; else { t=(bitree *) malloc (sizeof(bitree)); t->data=x; t->lch=creat(); t->rch=creat();} return t; }

  34. 2、基于递归遍历算法2 先序递归算法应用实例:先序建立二叉树(续) 主程序调用: main() { bitree *root; root=creat(); } 例:建立如图二叉树应该如何输入? 练习: 测试用例:1 2 3 0 0 4 0 5 0 0 6 0 0 问:画出建立的二叉树?

  35. 2、基于递归遍历算法3 算法5-5 二叉树中序遍历递归算法。 已知二叉树Bt,对其进行中序遍历,若二叉树为空,则为空操作;否则进行如下操作:中序遍历二叉树的左子树;访问二叉树根结点;中序遍历二叉树的右子树。 00 Indt_Bt(bitree *Bt) 01 { 02 if (Bt != NULL) 03 { 04 Indt_Bt(Bt->lch); /* 中序遍历左子树 */ 05 printf ("%c", Bt->data); /* 访问根结点 */ 06 Indt_Bt(Bt->rch); /* 中序遍历右子树 */ 07 } 08 }

  36. 2、基于递归遍历算法3 基于递归调用中序遍历:

  37. 2、基于递归遍历算法4 算法5-6 二叉树后序遍历递归算法。 已知二叉树Bt,对其进行后序遍历,若二叉树为空,则为空操作;否则进行如下操作:后序遍历二叉树的左子树;后序遍历二叉树的右子树;访问二叉树根结点。 00 Postv_Bt(bitree *Bt) 01 { 02 if (Bt != NULL) 03 { 04 Postv_Bt(Bt->lch); /* 后序遍历左子树 */ 05 Postv_Bt(Bt->rch); /* 后序遍历右子树 */ 06 printf ("%c", Bt->data); /* 访问根结点 */ 07 } 08 }

  38. 2、基于递归遍历算法4 基于递归调用后序遍历:

  39. §5.3 二叉树的遍历3 3、基于非递归遍历算法 非递归遍历算法中,需要做出三条假设: ① 设置一个一维数组Ss作为顺序栈以临时保存遍历时遇到的结点信息,其栈顶指针为Ss-top,初始时为0。 ② 采用二叉链表结构保存需要遍历的二叉树,起始指针为Bt,每个结点包含Data、Lchild和Rchild等三个域。 ③ 对结点进行的“访问”理解为将该结点的Data域的值打印出来。

  40. 3、基于非递归遍历算法2 00 Pre_Bt(bitree *Bt) 01 { 02 bitree *p; 03 bitree *stack[10]; /* 定义栈数组 */ 04 int top=-1; /* 定义栈顶下标top并赋初值-1 */ 05 printf("\nOutput preorder:"); 06 p= Bt; 07 while(p!=NULL || top!=-1) 08 if (p!=NULL) 09 { 10 printf("%d ",p->data); /* 访问该结点 */ 11 top=top+1;stack[top]=p; /* 访问后入栈 */ 12 p=p->lch; /* 继续深入左孩子 */ 13 } 14 else 15 { 16 p=stack[top];top=top-1; /* 遇空出栈,栈顶给p */ 17 p=p->rch; /* 转向右孩子 */ 18 } 19 } 算法5-7 先序遍历二叉树的非递归算法。 已知二叉树Bt,顺序栈Ss,要求打印出该二叉树的先序遍历序列。

  41. 3、基于非递归遍历算法2 基于非递归二叉树先序遍历:

  42. 3、基于非递归遍历算法3 中序非递归遍历算法流程: bitree *p; 定义及初始化栈; p=root; while(p!=NULL || 栈不空) if (p!=NULL) {p进栈;p=p->lch;} else {栈顶给p并出栈;输出p;p=p->rch;}

  43. 3、基于非递归遍历算法3 00 In_Bt(bitree *Bt) 01 { 02 bitree *stack[10]; /* 定义栈数组 */ 03 int top=-1; /* 定义栈顶下标top并赋初值-1 */ 04 bitree *ptr; 05 ptr = Bt; /* ptr是工作指针 */ 06 do 07 { 08 while (ptr != NULL) /* 一直朝左子树深入下去 */ 09 { 10 top++; /* 调整栈顶指针 */ 11 stack[top] = ptr; /* ptr所指结点进栈 */ 12 ptr = ptr->lch; 13 } 14 if (Ss_top !=-1 ) 15 { 16 ptr = stack[top] ; /* 栈顶元素赋值给ptr */ 17 top -- ; /* 出栈 */ 18 printf ("%d ", ptr->data); /* 访问该结点 */ 19 ptr = ptr->rch; /* 进入右子树访问 */ 20 } 21 }while((ptr !=NULL)||(top!=-1)); 22 } 算法5-8 中序遍历二叉树的非递归算法。 已知二叉树Bt,顺序栈Ss,要求打印出该二叉树的中序遍历序列。

  44. 3、基于非递归遍历算法3 基于非递归二叉树中序遍历(1-4趟):

  45. 3、基于非递归遍历算法3 基于非递归二叉树中序遍历(1-4趟): 46

  46. 3、基于非递归遍历算法4 算法5-9 后序遍历二叉树的非递归算法(略)。 已知二叉树Bt,顺序栈Ss,要求打印出该二叉树的后序遍历序列。 算法要点: 由于后序遍历是“左、右、根”,因此在后序遍历过程中搜索到某个结点时,不是马上访问它,而是将其作为相应子树根结点保存在工作栈中,然后沿着其左子树继续深入直到“最左下”结点。完成对其左子树访问后,从工作栈顶元素中获得相应根结点信息,但仍然不能马上进行访问,而是在工作栈中对其进行第二次保存,同时对其右子树进行遍历。在访问完右子树后,从工作栈中得到根结点信息,由此实现对相应根结点访问。

  47. 3、基于非递归遍历算法4 基于非递归二叉树后序遍历第1-3趟运算变化:

  48. 3、基于非递归遍历算法4 基于非递归二叉树后序遍历第4趟运算变化:

  49. 3、基于非递归遍历算法4 基于非递归二叉树后序遍历第5-7趟运算变化:

More Related