1 / 92

数据结构

数据结构. 2014 年 3 月. 第六章 树和二叉树. 6.1 树的概念和基本术语 6.2 二叉树 6.3 遍历二叉树和线索二叉树 6.4 树与森林 6.6 霍夫曼树. 6.1 树的概念和基本术语. 一、树的定义 树是由 n (n  0) 个结点的有限集合。如果 n = 0 ,称为空树;如果 n > 0 ,则 有且仅有一个特定的称之为根 (Root) 的结点,它只有直接后继,但没有直接前驱;

leigh-cote
Download Presentation

数据结构

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. 数据结构 2014年3月

  2. 第六章 树和二叉树 6.1树的概念和基本术语 6.2二叉树 6.3遍历二叉树和线索二叉树 6.4树与森林 6.6霍夫曼树

  3. 6.1树的概念和基本术语 • 一、树的定义 • 树是由 n (n  0) 个结点的有限集合。如果 n = 0,称为空树;如果 n > 0,则 • 有且仅有一个特定的称之为根(Root)的结点,它只有直接后继,但没有直接前驱; • 当n > 1,除根以外的其它结点划分为 m (m >0) 个互不相交的有限集 T1, T2 ,…, Tm,其中每个集合本身又是一棵树,并且称为根的子树(SubTree)。

  4. A B C D E F G H I J K L M 例如 A 只有根结点的树 有13个结点的树 其中:A是根;其余结点分成三个互不相交的子集, T1={B,E,F,K,L}; T2={C,G}; T3={D,H,I,J,M}, T1,T2,T3都是根A的子树,且本身也是一棵树

  5. 1 3 2 1 4 5 7 2 4 5 3 7 1 2 4 5 3 7 二、树的表示 • 1、树型表示法 • 2、集合表示法 • 3、括号表示法 (1(2(4,5),3(7))) • 4、凹入表示法

  6. 1层 A 2层 B C D height = 4 3层 E F G H I J 4层 K L M 三、树的基本术语 结点 结点的度 叶结点 分支结点 树的深度 树的度 森林 子女 双亲 兄弟 祖先 子孙 结点层次

  7. 1 3 2 4 5 7 6 8 9 10 11 12 树的基本术语 树的结点:包含一个数据元素及若干指向子树的分支;孩子结点:结点的子树的根称为该结点的孩子;双亲结点:B结点是A结点的孩子,则A结点是B结点的双亲;兄弟结点:同一双亲的孩子结点;堂兄结点:同一层上结点;结点层:根结点的层定义为1;根的孩子为第二层结点,依此类推;树的高度:树中最大的结点层结点的度:结点子树的个数树的度: 树中最大的结点度。叶子结点:也叫终端结点,是度为0的结点;分枝结点:度不为0的结点;森林;互不相交的树集合;有序树:子树有序的树,(子树不能互换)如:家族树;无序树:不考虑子树的顺序;

  8. 1 3 2 4 5 7 6 8 9 10 11 12 四、树的基本操作 1)InitTree(&T); 2)DestroyTree(&T); 3)CreateTree(&T, definition); 4)ClearTree(&T); 5)TreeEmpty(T); 6)TreeDepth(T); 7) Root(T); 8) Value(T, &cur_e); 9) Assign(T, cur_e, value); 10)Paret(T, cur_e); 11)LeftChild(T, cur_e); 12)RightSibling(T, cur_e); 13)InsertChild(&T, &p, i, c); 14)DeleteChild(&T,&p, i); 15)TraverseTree(T, Visit( ));

  9. L R L R 6.2二叉树 (Binary Tree) 一、定义 一棵二叉树是结点的一个有限集合,该集合或者为空,或者是由一个根结点加上两棵分别称为左子树和右子树的、互不相交的二叉树组成。 特点 每个结点至多只有两棵子树(二叉树中 不存在度大于2的结点) 五种形态

  10. c e a d b f + / - - * 应用举例 例 可以用二叉树表示表达式 具有三个结点的树和二叉树的形态各有几种? a+b*(c-d)-e/f

  11. 1层 A 2层 B D height = 4 3层 H I E F 4层 L J K 二、性质 性质1在二叉树的第 i 层上至多有 2i -1个结点。(i 1) [证明用归纳法] 证明:当i=1时,只有根结点,2 i-1=2 0=1。 假设对所有j,i>j1,命题成立,即第j层上至多有2 j-1个结点。 由归纳假设第i-1层上至多有 2i -2个结点。 由于二叉树的每个结点的度至多为2,故在第i层上的最大结点数为第i-1层上的最大结点数的2倍,即2* 2i -2= 2 i-1。

  12. 1层 A 2层 B D = =20 + 21 + … + 2 k-1 = 2 k-1 height = 4 3层 H I E F 4层 L J K • 性质2深度为 k 的二叉树至多有 2 k-1个结点(k  1)。 • 证明:由性质1可见,深度为k的二叉树的最大结点数为

  13. 1层 A 2层 B D height = 4 3层 H I E F 4层 L J K 性质3对任何一棵二叉树T, 如果其叶结点数为 n0, 度为2的结点数为 n2,则n0=n2+1. 证明:若度为1的结点有 n1个,总结点个数为 n,总边数为 e,则根据二叉树的定义, n = n0 + n1 + n2 e = 2n2 + n1 = n - 1 因此,有 2n2 + n1 = n0 + n1 + n2- 1 n2 = n0- 1 n0 = n2 + 1

  14. 1 3 2 4 5 7 6 8 9 10 11 12 13 14 15 两种特殊形态的二叉树 定义1满二叉树 (Full Binary Tree) 一棵深度为k且有2 k-1个结点的二叉树称为满二叉树。 满二叉树

  15. 1 1 3 3 2 2 4 5 4 5 6 6 7 1 3 2 4 5 7 6 8 9 10 11 12 定义2完全二叉树 (Complete Binary Tree) 若设二叉树的高度为h,则共有h层。除第 h 层外,其它各层 (0  h-1) 的结点数都达到最大个数,第 h 层从右向左连续缺若干结点,这就是完全二叉树。 完全二叉树 非完全二叉树

  16. 1 3 2 4 5 7 6 8 9 10 11 12 性质4具有 n (n  0) 个结点的完全二叉树的深度为log2(n) +1 证明: 设完全二叉树的深度为 h,则根据性质2和完全二叉树的定义有 2h-1- 1 < n  2h- 1或 2h-1 n < 2h 取对数 h-1 < log2n  h,又h是整数, 因此有 h = log2(n) +1

  17. 0 1 3 1 2 2 4 5 7 3 4 5 6 6 8 9 10 11 12 8 9 7 • 性质5如将一棵有n个结点的完全二叉树自顶向下,同一层自左向右连续给结点编号0, 1, 2, …, n-1,则有以下关系: • 若i = 0, 则 i 无双亲 若i > 0, 则 i 的双亲为(i -1)/2 • 若2*i+1 < n, 则 i 的左子女为 2*i+1,若2*i+2 < n, 则 i 的右子女为2*i+2 • 若结点编号i为偶数,且i!=0,则左兄弟结点i-1. • 若结点编号i为奇数,且i!=n-1,则右兄弟结点为i+1. • 结点i 所在层次为log2(i+1)  性质5:在完全二叉树中编号(1开始)为i的结点, 1)若有左孩子,则左孩编号为2i; 2)若有右孩子,则有孩子结点编号为2i+1; 3)若有双亲,则双亲结点编号为i/2;

  18. 顺序表示 1 1 2 3 2 3 4 5 6 7 4 5 6 8 9 7 8 9 10 10 9 三、二叉树的存储结构 1 2 3 4 5 6 7 8 910 1 2 3 4 0 5 6 7 8 0 0 910 完全二叉树 一般二叉树 的顺序表示 的顺序表示

  19. lChild data rChild parent data data rChild lChild rChild lChild • 链表表示 含两个指针域的结点结构 lChild data parent rChild 含三个指针域的结点结构 二叉链表 三叉链表

  20. 二叉树链表表示的示例 root root root A  A   A B B B  D   D  C D C C         E F E F E F 二叉树 二叉链表 三叉链表

  21. 四、二叉链表的定义 typedef struct BiTNode { //结点定义 TElemType data; struct BiTNode* lchild, * rchild; } BiTNode,*BiTree; BiTree指针数据类型作为二叉树的数据类型。

  22. 6.3 二叉树的遍历 一、定义 1、遍历:按某种搜索路径访问二叉树的每个结点,而且每个结点仅被访问一次。 2、访问:含义很广,可以是对结点的各种处理,如修改结点数据、输出结点数据。 遍历是各种数据结构最基本的操作,许多其他的操作可以在遍历基础上实现。对于线性结构由于每个结点只有一个直接后继,遍历是很容易的事 二叉树是非线性结构,每个结点可能有两个后继,如何访问二叉树的每个结点,而且每个结点仅被访问一次?

  23. D E C G B A F 二、二叉树的遍历方法 二叉树由根、左子树、右子树三部分组成 二叉树的遍历可以分解为:访问根,遍历左子树和遍历右子树 令:L:遍历左子树D:访问根结点R:遍历右子树 有六种遍历方法: DLR,LDR,LRD, DRL,RDL,RLD 约定先左后右,有三种遍历方法: DLR、LDR、LRD ,分别称为先序遍历、中序遍历、后序遍历

  24. D E C G B A F 1、先序遍历(DLR) 若二叉树非空 (1)访问根结点; (2)先序遍历左子树; (3)先序遍历右子树; 例:先序遍历右图所示的二叉树 (1)访问根结点A (2)先序遍历左子树:即按DLR的顺序遍历左子树 (3)先序遍历右子树:即按DLR的顺序遍历右子树 先序遍历序列:A,B,D,E,G,C,F

  25. D E C G B A F 2、中序遍历(LDR) 若二叉树非空(1)中序遍历左子树(2)访问根结点 (3)中序遍历右子树 例:中序遍历右图所示的二叉树 (1)中序遍历左子树:即按LDR的顺序遍历左子树 (2)访问根结点A (3)中序遍历右子树:即按LDR的顺序遍历右子树 中序遍历序列: D,B,G,E,A,C,F

  26. D E C G B A F 3、后序遍历(LRD) 若二叉树非空(1)后序遍历左子树(2)后序遍历右子树 (3)访问根结点 例:后序遍历右图所示的二叉树 (1)后序遍历左子树:即按LRD的顺序遍历左子树 (2)后序遍历右子树:即按LRD的顺序遍历右子树 (3)访问根结点A 后序遍历序列: D,G,E,B,F,C,A

  27. - + / * a e f b - c d 例:先中序遍历序遍历、中序遍历、后序遍历下图所示的二叉树 先序遍历序列:-,+,a,*,b,-,c,d,/,e,f 中序遍历序列:a,+,b,*,c,-,d,-,e,/,f 后序遍历序列:a,b,c,d,-,*,+,e,f,/,-

  28. 二. 遍历的递归算法 上面介绍了三种遍历方法,显然是用递归的方式给出的三种遍历方法,以先序为例: 先序遍历(DLR)的定义: 先序遍历递归定义 递归项 若二叉树非空 (1)访问根结点; (2)先序遍历左子树 (3)先序遍历右子树; 该定义隐含着若二叉树为空,结束 这实际上是先序遍历的递归定义,我们知道递归定义包括两个部分: 1)基本项(也叫终止项); 2)递归项

  29. 上面先序遍历的定义等价于: 若二叉树为空,结束 ——基本项(也叫终止项) 若二叉树非空 ——递归项 (1)访问根结点; (2)先序遍历左子树 (3)先序遍历右子树; 下面给出先序、中序、后序遍历递归算法,为了增加算法的可读性,这里对书上算法作了简化,没有考虑访问结点出错的情况(即我们假设调用函数visit( )访问结点总是成功的。

  30. T A B∧ C∧ ∧E∧ ∧D ∧F∧ • 先序遍历递归算法void PreOrderTraverse(BiTree T, Status(*Visit)(TElemType e)) {//采用二叉链表存贮二叉树, visit( )是访问结点的函数。本算法先序 • //遍历以为根结点指针的二叉树,对每个数据元素调用函数Visit( ) if (T) { //若二叉树为空,结束返回// 若二叉树不为空,访问根结点;遍历左子树,遍历右子树 • Visit(T->data);PreOrderTraverse(T->lchild, Visit); PreOrderTraverse(T->rchild, Visit); }//PreOrderTraverse • 最简单的Visit函数是:Status PrintElement(TElemType e) • { //输出元素e的值printf(e); • return OK; • }

  31. 2 中序遍历递归算法 void InOrderTraverse(BiTree T, Status(*Visit)(TElemType e)) {//采用二叉链表存贮二叉树, visit( )是访问结点的函数。本算法中序 //遍历以为根结点指针的二叉树,对每个数据元素调用函数Visit( )if (T) { //若二叉树为空,结束返回// 若二叉树不为空,遍历左子树,访问根结点,遍历右子树InOrderTraverse(T->lchild, Visit); Visit(T->data); InOrderTraverse(T->rchild, Visit); }// InOrderTraverse 你能写出后序遍历递归算法了吧

  32. 3 后序遍历递归算法 void PostOrderTraverse(BiTree T, Status(* Visit)(TElemType e)) { //采用二叉链表存贮二叉树, visit( )是访问结点的函数。本算法中序 //遍历以为根结点指针的二叉树,对每个数据元素调用函数Visit( ) if (T) { //若二叉树为空,结束返回// 若二叉树不为空,遍历左子树,遍历右子树,访问根结点PostOrderTraverse(T->lchild, Visit); PostOrderTraverse(T->rchild, Visit); Visit(T->data); }//PostOrderTraverse

  33. A B∧ C∧ ∧E∧ ∧D ∧F∧ 4. 层次遍历递归算法 void levelorder(Bitree bt) { Bitree queue[MAXNODE]; int front,rear; if(bt==NULL) return; front=-1; rear=0; queue[rear]=bt; while(front!=rear) { front++; cout<<queue[front]->data<<" "; if(queue[front]->lchild!=NULL) { rear++; queue[rear]=queue[front]->lchild; } if(queue[front]->rchild!=NULL) { rear++; queue[rear]=queue[front]->rchild; } } }

  34. 三、二叉树遍历应用 例1:为二叉树建立二叉链表(递归算法)以递归方式建立二叉树 输入:二叉树的先序序列 结果:二叉树的二叉链表 遍历操作访问二叉树的每个结点,而且每个结点仅被访问一次。是否可在利用遍历,建立二叉链表的所有结点并完成相应结点的链接? 基本思想:输入(在空子树处添加*的二叉树的)先序序列(设每个元素是一个字符//)按先序编历的顺序,建立二叉链表的所有结点并完成相应结点的链接

  35. E E B D F B D F A A C C A * * * * * * * B∧ C∧ ∧E∧ ∧D ∧F∧ 先序序列:A B D F C E T (在空子树处添加*的二叉树的)先序序列: A B D * F * * C E * * * *

  36. Status CreateBiTree(BiTree &T) { //输入(在空子树处添加*的二叉树的)先序序列(设每个元素是一个字 // 符)按先序编历的顺序,建立二叉链表,并将该二叉链表根结点指针 //赋给T scanf (&ch); if (ch= = ‘* ’) T=NULL; // 若ch= = ‘*’ 则T=NULL返回 else { // 若ch! = ‘*’ if (! (T=(BiTNode * )malloc(sizeof(BiTNode)))) exit(OVERFLOW); T->date = ch; // 建立(根)结点 CreateBiTree(T->lchild); //构造左子树链表,并将左子树根结点指针 //赋 给(根)结点 的左孩子域 CreateBiTree(T->rchild); //构造右子树链表,并将右子树根结点指针 //赋给(根)结点 的右孩子域 } return OK; }//CreateBiTree

  37. 2. 计算二叉树结点个数(递归算法) int Count(BiTtree T) { if (T==NULL) return 0; else return 1 + Count (T->lchild)+Count(T->rchild); }

  38. 3. 求二叉树中叶子结点的个数 int Countleaf(BiTree T ) //求二叉树中叶子结点的数目 { if ( T==NULL ) return 0; //空树没有叶子 if(T->lchild==NULL&&T->rchild==NULL) return 1; //叶子结点 return Countleaf(T->lchild)+Countleaf(T->rchild); //左子树的叶子数加上右子树的叶子数 }

  39. 4. 求二叉树高度(递归算法) int Height ( Bitree T ) //Height of a tree { if ( T == NULL ) return -1; else { int m = Height ( T->lchild ); int n = Height ( T->rchild ); return (m > n) ? m+1 : n+1; } }

  40. 5. 复制二叉树(递归算法) Bitree Copy( Bitree T ) // copy a tree { if ( T == NULL ) return NULL; Bitree Temp=new Bitnode; Temp->data=T->data; Temp->lchild = Copy( T->lchild ); Temp->rchild = Copy(T->rchild ); return Temp; }

  41. 6. 判断二叉树等价(递归算法) int Equal( BinTreeNode *a, BinTreeNode *b) { if ( a == NULL && b == NULL ) return 1; if ( a != NULL && b !=NULL && a->data==b->data && Equal( a->leftChild, b->leftChild) && Equal( a->rightChild, b->rightChild) ) return 1; return 0;//如果a和b的子树不等同,则函数返回0 }

  42. 7. 已知一棵二叉树的中根序列和先根序列分别为ABCDEFGHIJK和EBADCFHGIKJ,试画出这棵二叉树。7. 已知一棵二叉树的中根序列和先根序列分别为ABCDEFGHIJK和EBADCFHGIKJ,试画出这棵二叉树。 从先根序列EBADCFHGIKJ可确定根结点是E。因中根序列是先遍历左子树,再遍历根结点,最后遍历右子树,所以从中根序列ABCDEFGHIJK可确定对应的二叉树的左子树由A,B,C,D结点组成,二叉树的右子树由F,G,H,I,J,K结点组成,二叉树的组成示意图如图 (a)所示。

  43. 二叉树的左子树也是一棵二叉树,它的先根序列可从整个二叉树的先根序列EBADCFHGIKJ 中找到,为BADC,由此可确定该左子树的根结点是B。从中根序列中的ABCD可确定B结点的左子树由A结点组成,B结点的右子树由C,D结点组成,二叉树的组成示意图可进一步展开,如图 (b)所示。 该思路是个递归的思路,依此类推可得二叉树,如图 (c)所示。 中根序列为ABCDEFGHIJK和先根序列为EBADCFHGIKJ

  44. - b - * c a - - - * c c * c * c * b b a a b a b a -*abc a*b-c ab*c-

  45. a d a c b a a e d c 中序遍历二叉树(非递归算法)用栈实现 b a b退栈 访问 d退栈 访问 d入栈 a b入栈 e c c退栈 访问 a退栈 访问 e 退栈 访问 c e入栈 栈空

  46. a c b e d void InOrder ( BinTree T ) { stack S; InitStack( &S ); //递归工作栈 BinTreeNode *p = T;//初始化 while ( p != NULL || !StackEmpty(&S) ) { if( p != NULL ) { Push(&S, p); p = p->leftChild; } if ( !StackEmpty(&S) ) { //栈非空 Pop(&S, p); //退栈 cout << p->data << endl; //访问根 p = p->rightChild; }//if }//while return ok; }

  47. 四、线索二叉树(Threaded Binary Tree) 1 线索二叉树遍历是二叉树最基本最常用的操作。1)对二叉树所有结点做某种处理可在遍历过程中实现;2)检索(查找)二叉树某个结点,可通过遍历实现; 与线性表相比,对二叉树的遍历存在如下问题: 1)遍历算法要复杂的多,费时的多; 2)为检索或查找二叉树中某结点在某种遍历下的后继,必须从根开始遍历,直到找到该结点及后继; 如果能将二叉树线性化,就可以减化遍历算法,提高遍历速度。 如何将二叉树线性化? 以中序遍历为例,我们看到通过遍历可以得到二叉树结点的中序序列。若能将中序序列中每个结点前趋、后继信息保存起来,以后再遍历二叉树时就可以根据所保存的结点前趋、后继信息对二叉树进行遍历。 加上结点前趋后继信息(线索)的二叉树称为线索二叉树

  48. G C E A H D B F 孩子指针 前趋指针 后继指针 线索二叉树 中序遍历序列:D,B,H,E,A,F,C,G 在中序序列中,D的后继是B,H的前趋是B,后继是E… 加上结点前趋后继信息(结索)的二叉树称为线索二叉树

  49. A T B E∧ ∧G∧ C ∧ F∧ ∧D ∧H ∧ 2.线索链表线索二叉树的存贮方法:1) 为每个结点增加二个指针域。缺点:要点用较多的内存空间。 每个n个结点的二叉链表,有n+1个空指针域,故可利用这些的空指针域存放结点的前趋和后继指针,以这种结点的构成二叉链表称为线索链表

  50. data rchild lchild Ltag Rtag 结点结构 Ltag=0, lchild为左子女指针 Ltag=1, lchild为前驱线索 Rtag=0, rchild为右子女指针 Rtag=1, rchild为后继指针

More Related