670 likes | 864 Views
第六章 树和二叉树. 树的概念与定义 二 叉 树 二叉树的遍历与线索化 树和森林 哈夫曼树及其应用 树的计数 *. 6. 1 树的概念与定义. 树的定义: 树 (tree) 是 n(n≥0) 个结点的有限集 T ,当 n=0 时,称为空树;当 n>0 时,满足以下条件: ( 1 )有且仅有一个结点被称为树根( root )结点; ( 2 )当 n>1 时,除根结点以外的其余 n-1 个结点可以划分成 m(m>0) 个互不相交的有限集 T1,T2, … , Tm ,其中每一个集合本身又是一棵树,称为根的子树 (subtree) 。.
E N D
第六章 树和二叉树 • 树的概念与定义 • 二 叉 树 • 二叉树的遍历与线索化 • 树和森林 • 哈夫曼树及其应用 • 树的计数*
6. 1树的概念与定义 树的定义:树(tree)是n(n≥0)个结点的有限集T,当n=0时,称为空树;当n>0时,满足以下条件: (1)有且仅有一个结点被称为树根(root)结点; (2)当n>1时,除根结点以外的其余n-1个结点可以划分成m(m>0)个互不相交的有限集T1,T2,…,Tm,其中每一个集合本身又是一棵树,称为根的子树(subtree)。
结点(node):表示树中的元素,包括数据项及若干指向其子树的分支。结点(node):表示树中的元素,包括数据项及若干指向其子树的分支。 • 结点的度(degree):结点拥有的子树的数目。图6.1中结点A的度为3。 • 叶子(leaf):度为0的结点称为叶子结点,也称为终端结点。图6.1中,叶子结点有:K,L,F,G,M,I,J。 • 分支结点:度不为0的结点称为分支结点,也称为非终端结点。图6.1中,非终端结点有:A,B,C,D等。
孩子结点(child):结点的子树的根称为该结点的孩子结点。图6.1中,结点A的孩子结点为B,C,D,结点B的孩子结点为E,F。 • 双亲结点(parents):孩子结点的上层结点称为该结点的双亲结点。图6.1中,结点I的双亲为D,结点L的双亲为E。 • 兄弟结点(sibling):具有同一双亲结点的孩子结点之间互称为兄弟结点。图6.1中,结点B,C,D互为兄弟,结点K,L互为兄弟。
树的度:树中最大的结点的度数即为树的度。图6.1中的树的度为3。树的度:树中最大的结点的度数即为树的度。图6.1中的树的度为3。 • 结点的层次(level):从根结点算起,根为第一层,它的孩子为第二层……。若某结点在第l层,则其孩子结点就在第l+1层。图6.1中,结点A的层次为1,结点M的层次为4。 • 树的高度(depth):树中结点的最大层次数。图6.1中的树的高度为4。 • 森林(forest):m(m≥0)棵互不相交的树的集合。
有序树与无序树:树中结点的各子树从左至右是有次序的(不能互换)则称该树为有序树,否则称该树为无序树。有序树与无序树:树中结点的各子树从左至右是有次序的(不能互换)则称该树为有序树,否则称该树为无序树。
6.2 二叉树 • 二叉树的定义:二叉树是由n(n≥0)个结点的有限集T构成,此集合或者为空集,或者由一个根结点及两棵互不相交的左右子树组成,并且左右子树都是二叉树。 • 注意:二叉树的子树有左右之分,因此二叉树是一种有序树。
二叉树的性质: • 性质1 在二叉树的第i层上至多有2i-1个结点(i≥1)。 • 性质2 深度为k的二叉树至多有2k-1个结点(k>=1)。 • 性质3对任意一棵二叉树BT,如果其叶子结点数为n0,度为2的结点数为n2,则n0=n2+1。 • 性质4 具有n个结点的完全二叉树的深度为┗log2n┛+1。(符号┗x┛表示不大于x的最大整数。)
二叉树的性质: • 性质5对于具有n个结点的完全二叉树,如果对其结点按层次编号,则对任一结点i(1≤i≤n),有: (1) 如果i=1,则结点i是二叉树的根,无双亲;如果i>1,则其双亲是┗i/2┛ (2) 如果2i>n,则结点i无左孩子;如果2i≤n,则其左孩子是2i (3) 如果2i+1>n,则结点i无右孩子;如果2i+1≤n,则其右孩子是2i+1
满二叉树:一棵深度为k且有2k-1个结点的二叉树称为满二叉树。满二叉树:一棵深度为k且有2k-1个结点的二叉树称为满二叉树。 • 完全二叉树:深度为k,有n个结点的二叉树当且仅当其每一个结点都与深度为k的满二叉树中编号从1至n的结点一一对应时,称为完全二叉树。 • 请判断在下列二叉树中,哪些是完全二叉树?哪些不是完全二叉树?
1 2 3 1 4 5 2 3 4 5 6 7 6 7 8 9 10 11 12 13 14 15 1 1 2 3 2 3 4 5 6 7 6 4 5 8 9 10 11 12
二叉树的存储结构 a b c d e φ φ φ φ f g • 顺序存储结构 :为了能够反映出结点之间的逻辑关系,必须将它“修补”成完全二叉树,对应该完全二叉树,可以开辟长度为11的数组,对11个数据元素进行存储,原二叉树中空缺的结点在数组中的相应单元必须置空
Lchild data rchild 二叉树的存储结构 • 链式存储结构 :表示二叉树的链表中的结点应该包含3个域:数据域和指向左、右子树的指针域,二叉树的这种存储结构被称为二叉链表。 在n个结点的二叉链表中,有n+1个空指针域
6.3 二叉树的遍历与线索化 二叉树的遍历:是按某条搜索路径访问树中的每一个结点,使得每一个结点均被访问一次,而且仅被访问一次。 • 先根遍历二叉树 (1)访问根结点; (2)先根遍历左子树; (3)先根遍历右子树。
中根遍历二叉树 (1)中根遍历左子树; (2)访问根结点; (3)中根遍历右子树。 • 后根遍历二叉树 (1)后根遍历左子树; (2)后根遍历右子树; (3)访问根结点。
算术表达式的二叉树表示及其三种遍历序列 先根遍历: -+a*b–cd/ef 中根遍历: a+b*c–d–e/f 后根遍历: abcd-*+ef/-
二叉树在二叉链表存储结构下的数据类型定义如下:二叉树在二叉链表存储结构下的数据类型定义如下: typedef struct Node { datatype data; struct Node *Lchild; struct Node *Rchild; } BTnode,*Btree;
先根遍历算法 • void preorder(Btree root) • { • if(root!=NULL) • { • Visit(root->data); • preorder(root->Lchild); • preorder(root->Rchild); • } • }
中根遍历算法 • void InOrder(Btree root) • { • if(root!=NULL) • { • InOrder(root->Lchild); • Visit(root->data); • InOrder(root->Rchild); • } • }
后根遍历算法 • void PostOrder(Btree root) • { • if(root!=NULL) • { • PostOrder(root->Lchild); • PostOrder(root->Rchild); • Visit(root ->data); • } • }
Lchild Ltag Data Rtag Rchild 线索二叉树:利用二插链表剩余的n+1个空指针域来存放遍历过程中结点的前驱和后继的指针,这种附加的指针称为“线索”,加上了线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树。 为了区分结点的指针域是指向其孩子的指针,还是指向其前驱或后继的线索,可在二叉链表的结点中,再增设2个标志域。
0 Lchild域指示结点的左孩子Ltag= 1 Lchild域指示结点的遍历前驱 0 Rchild域指示结点的右孩子Rtag= 1 Rchild域指示结点的遍历后继
二叉树以及它的中序线索二叉树 (a) 二叉树 (b) 中序线索二叉树
基于遍历的应用与线索二叉树的应用 二叉树的遍历是对二叉树进行各种运算的一个重要基础,对访问(程序中的visit函数)结点可理解为各种对二叉树中结点进行操作。因此,只要将二叉树三种遍历算法中的visit函数具体化,就产生了基于二叉树的不同应用。
输出二叉树中的结点(先根遍历实现) Void paintnode (Btree root) { if (root!=NULL) { printf (root ->data); paintnode (root ->Lchild); paintnode (root ->Rchild); } }
输出二叉树中的叶子结点(先根遍历实现) Void paintleaf (Btree root) { if (root!=NULL) { if(root->Lchild==NULL && root->Rchild==NULL) printf (root ->data); paintleaf (root ->Lchild); paintleaf (root ->Rchild); } }
统计叶子结点数目(后根遍历实现) Void leafcount(Btree root) { if(root!=NULL) { leafcount(root->Lchild); leafcount(root->Rchild); if (root ->Lchild==NULL && root ->Rchild==NULL) count++; } } 提示:count为全局变量,在主函数中定义。
建立二叉树 图中二叉树的先根遍历序列为:ABDGCEHF,而考虑空子树后的先根遍历序列应为:ABD.G.CE.HF,其中“.”代表空子树。
如果已知二叉树的考虑了空子树后的遍历序列,那么建立这棵二叉树的算法如下(假定datatype类型为char):如果已知二叉树的考虑了空子树后的遍历序列,那么建立这棵二叉树的算法如下(假定datatype类型为char): Void CreateBtree(Btree *bt) { char ch; ch=getchar(); if(ch=='.') *bt=NULL; else { *bt=(Btree)malloc(sizeof(BTnode)); (*bt)->data=ch; CreateBiTree(&((*bt)->Lchild)); CreateBiTree(&((*bt)->Rchild)); } }
求二叉树的高度 采用递归的方法定义二叉树的高度: (1)若二叉树为空,则高度为0; (2)若二叉树非空,其高度应为其左右子树高度的最大值加1。 int TreeDepth(Btree bt) { int hl,hr,max; if(bt!=NULL) { hl=TreeDepth(bt->Lchild); hr=TreeDepth(bt->Rchild);
max=(hl,hr); return(max+1); } else return(0); }
在中根遍历的线索树中查找前驱结点 对于二叉树中任意结点p,要找其前驱结点,当p->Ltag=1时,p->Lchild即为p的前驱结点;当p->Ltag=0时,说明p有左子树,此时p的中根遍历下的前驱结点即为其左子树右链下的最后一个结点。 Void Previous(ThreadTnode * p, ThreadTnode *pre) {ThreadTnode *q; if(p->Ltag==1) pre= p->Lchild; else { for(q= p->Lchild;q->Rtag==0;q=q->Rchild); pre=q; } }
在中根遍历的线索树中查找后继结点 二叉树中任意结点p,若要找其后继结点,当p->Rtag=1时,p->Rchild即为p的后继结点;当p->Rtag=0时,说明p有右子树,此时p的中根遍历下的后继结点即为其右子树左链下的最后一个结点。 Void Succedent(ThreadTnode *p, ThreadTnode *succ) {ThreadTnode *q; if (p->Rtag==1) succ= p-> RChild; else { for(q= p->RChild; q->Ltag==0 ;q=q->LChild ); succ=q; } }
6.4 树和森林 树的存储结构 • 双亲(链表)表示法 • 用一组连续的存储空间(数组)来存储树中的结点,每个数组元素中不但包含结点本身的信息,还保存该结点双亲结点在数组中的下标号。数组中每个结点含两个域: • 数据域:存放结点本身信息 • 双亲域:指示本结点的双亲结点在数组中位置
在双亲表示法下,树的数据类型定义如下:#define Maxsize 50typedef struct Node{ DataType data; int parent;}Tnode;Tnode Ptree[Maxsize];
a b c a f d e b c g h i d e f g h i 树的双亲(链表)表示法(图6.10) data parent -1 0 0 1 0 2 1 3 1 4 2 (a) 树 5 4 6 4 7 如何找孩子结点 4 8 (b) 树的双亲表示法下的存储结构
孩子链表表示法 把每个结点的孩子结点排列起来,构成一个单链表,该单链表就是本结点的孩子链表。具有n个结点的树就形成了n个孩子链表 • 表头结点类型 • typedef struct • { • DataType data; • ChildNode * ChildHead ; • }DataNode; • 孩子链表 DataNode Ctree[Maxsize]; • 孩子结点类型 #define Maxsize 50 • typedef struct ChildNode • { • int Child; • struct ChildNode * next; • }ChildNode;
FirstChild data Nextsibling • 孩子兄弟链表表示法 该表示法又称为二叉链表表示法,即以二叉链表作为树的存储结构。链表中每个结点设有两个链域,与二叉树的二叉链表表示法所不同的是,这两个链域分别指向该结点的第一个孩子结点和下一个兄弟(右兄弟)。 typedef struct CSNode { DataType data; Struct CSNode *FirstChild, *Nextsibling; }*CSTree;
图6.14 树的孩子兄弟链表表示法 树和二叉树之间的相互转换的关系如下:
对应 结论:树的孩子兄弟链表表示法与对应二叉树的二插链表表示法相同。因此,上图中的树与二叉树之间存在相互转换的关系。
A A A B B B C C C D D D A E E E F F F G G G H H H I I I B A E C F D B C D G H E F G H I I • 树转换为二叉树 • 加线:在树中所有相邻的兄弟之间加一连线; • 抹线:对树中每个结点,除了其左孩子外抹去该结点与其余孩子之间的连线。 • 整理:以树的根结点为轴心,将整树顺时针转45°。 树转换成的二叉树其右子树一定为空
G G G E G A H H H H I F B C D I I I J J J J A A A B E E E B B C F F F C C D D D • 森林转换成二叉树 • 将各棵树分别转换成二叉树 • 将每棵树的根结点用线相连 • 以第一棵树根结点为二叉树的根,再以根结点为轴心,顺时针旋转,构成二叉树型结构
A A A B B A B E C E C E C B C D F D F D A F D E F G H I G H B G H G H E C I I I F D G H I • 二叉树转换成树 • 加线:若p结点是双亲结点的左孩子,则将p的右孩子,右孩子的右孩子,……沿分支找到的所有右孩子,都与p的双亲用线连起来 • 抹线:抹掉原二叉树中双亲与右孩子之间的连线 • 调整:将结点按层次排列,形成树结构
G G G E G A H H H H I F B C D I I I J J J J A A A B B E E E B C C F F F C D D D • 二叉树转换成森林 • 抹线:将二叉树中根结点与其右孩子连线,及沿右分支 搜索到的所有右孩子间连线全部抹掉,使之变成孤立的二叉树 • 还原:将孤立的二叉树还原成树
树和森林的遍历 • 树的遍历 • 先根遍历 若树非空,则遍历方法为: a.访问根结点。 b.从左到右,依次先根遍历根结点的每一棵子树。 • 后根遍历 若树非空,则遍历方法为: a.从左到右,依次后根遍历根结点的每一棵子树。 b.访问根结点。 • 按层次遍历 若树非空,则遍历方法为: 先访问第一层上的结点,然后依次遍历第二层,……第n 层的结点。
A B C D G E F H J K L M I N O A B E F I G C D H J K L N O M 先根遍历: E I F G B C J K N O L M H D A 后根遍历: A B C D E F G H I J K L M N O 层次遍历:
森林的遍历 • 先根遍历 若森林非空,则遍历方法为: a.访问森林中第一棵树的根结点。 b.先根遍历第一棵树的根结点的子树森林。 c.先根遍历除去第一棵树之后剩余的树构成的森林。 • 中根遍历 若森林非空,则遍历方法为: a.中根遍历森林中第一棵树的根结点的子树森林。 b.访问第一棵树的根结点。 c.中根遍历除去第一棵树之后剩余的树构成的森林。 • 后根遍历 若森林非空,则遍历方法为: a.后根遍历森林中第一棵树的根结点的子树森林。 b.后根遍历除去第一棵树之后剩余的树构成的森林。 c.访问第一棵树的根结点。
图6.18(a)所示的森林 上述森林的三种遍历序列如下: 先根遍历:A B C D E F G H I J 中根遍历:B C D A F E H J I G 后根遍历:D C B F J I H G E A