700 likes | 823 Views
第 5 章 树和二叉树. 本章中主要介绍下列内容: 树的逻辑定义和存储结构 二叉树的逻辑定义、存储结构 二叉树的基本操作算法 树和二叉树的转换 哈夫曼树及其应用. 5.1 树的定义及相关术语 一、定义 树是一种常用的非线性结构。 我们可以这样定义:树是 n ( n≥0 )个结点的有限集合。 若 n=0 ,则称为空树;否则,有且仅有一个特定的结点被称为根 . 当 n>1 时,其余结点被分成 m ( m>0 )个互不相交的子集 T1 , T2 , ... , Tm ,每个子集又是一棵树。 由此可以看出,树的定义是递归。
E N D
第5章 树和二叉树 本章中主要介绍下列内容: • 树的逻辑定义和存储结构 • 二叉树的逻辑定义、存储结构 • 二叉树的基本操作算法 • 树和二叉树的转换 • 哈夫曼树及其应用
5.1 树的定义及相关术语 一、定义 树是一种常用的非线性结构。 我们可以这样定义:树是n(n≥0)个结点的有限集合。 若n=0,则称为空树;否则,有且仅有一个特定的结点被称为根. 当n>1时,其余结点被分成m(m>0)个互不相交的子集T1,T2,...,Tm,每个子集又是一棵树。 由此可以看出,树的定义是递归。 树的定义还可以形式化的描述为二元组的形式: T=(D,R) 其中,D为树T中节点的集合,R为树中结点之间关系的集合。
A A A A C B B C B D B D C C F D E G D E F E E F G F G H I (a) (b) (c) (d) 树具有以下两个特点: (1)树的根结点没有前驱结点,除根结点之外的所有结点有且只有一个前驱结点。 (2)树中的所有结点可以有零个或多个后继结点。
二、相关术语 结点: 数据元素的内容及其指向其子树根的分支统称为结点。 结点的度: 结点的分支数。 叶子结点(终端): 度为0的结点。 非终端结点: 度不为0的结点。 结点的层次: 树中根结点的层次为1,根结点子树的根为第2层,以此类推。 树的度: 树中所有结点度的最大值。 树的深度: 树中所有结点层次的最大值。 有序树、无序树: 如果树中每棵子树从左向右的排列拥有一定的顺序,不得互换,则称为有序树,否则称为无序树。 路径、路径长度:设n1, n2, …,nk,为一棵树的结点序列,若结点ni是ni+1的父亲结点,则把n1, n2, …,nk称为一条由n1至nk的路径。这条路径的长度是k-1。
森林:是m(m≥0)棵互不相交的树的集合。 在树结构中,结点之间的关系又可以用家族关系描述,定义如下: 孩子、双亲:结点子树的根称为这个结点的孩子,而这个结点又被称为孩子的双亲。 子孙:以某结点为根的子树中的所有结点都被称为是该结点的子孙。 祖先:从根结点到该结点路径上的所有结点。 兄弟:同一个双亲的孩子之间互为兄弟。 堂兄弟:双亲在同一层的结点互为堂兄弟。
树的基本运算 常用操作: (1)Initiate(t)初始化一棵空树t。 (2) Roo(x)求结点x所在树的根结点。 (3) Parent(t,x)求树t中结点x的双亲结点。 (4) Child(t,x,i)求树t中结点x的第i个孩子结点。 (5) RightSibling(t,x)求树t中结点x的第一个右边兄弟结点。 (6) Insert(t,x,i,s)把以s为根结点的树插入到树t中作为结点x的第i棵子树。 (7) Delete(t,x,i)在树t中删除结点x的第i棵子树。 (8) Traverse(t)是树的遍历操作。
5.2 二叉树 5.2.1 二叉树的基本概念 1. 定义 二叉树是一种特殊树形结构。它与树形结构的区别是 (1)每个结点最多有两棵子树; (2)子树有左右之分。 二叉树也可以用递归的形式定义。即:二叉树是n(n≥0)个结点的有限集合。 当n=0时,称为空二叉树;当n>0时,有且仅有一个结点为二叉树的根,其余结点被分成两个互不相交的子集,一个作为左子集,另一个作为右子集,每个子集又是一个二叉树。
A B C D E F G H I J K L M N O
A B C D E F G H I J
二叉树的5种形态: ø (d) (b) (c) (e) (a)
2、满二叉树和完全二叉树 满二叉树:在一棵二叉树中,如果所有分支结点都存在左子树和右子树,并且所有叶子结点都在同一层上,这样的一棵二叉树称做满二叉树。 完全二叉树:有一棵深度为k的有n个结点的二叉树,若将它与一棵同深度的满二叉树中的所有结点按从上到下,从左到右的顺序分别进行编号,且该二叉树中的每个结点分别与满二叉树中编号为1~n的结点位置一一对应,则称这棵二叉树为完全二叉树。
A B C D E F G H I J K L M N O
A B C D E F G H I J
5.2.2 二叉树的主要性质 二叉树具有下列5个重要的性质。 【性质1】 在二叉树的第i层上最多有2i-1个结点(i≥1)。 【性质2】 深度为K的二叉树最多有2K-1个结点(K≥1)。 【性质3】 对于任意一棵二叉树BT,如果度为0的结点个数为n0,度为2的结点个数为n2,则n0=n2+1。 【性质4】 具有n个结点的完全二叉树的深度为 log2n+1。其中,log2n的结果是不大于log2n的最大整数。
【性质5】 对于有n个结点的完全二叉树中的所有结点按从上到下,从左到右的顺序进行编号,则对任意一个结点i (1≤i≤n),都有: (1)如果i=1,则结点i是这棵完全二叉树的根,没有双亲;否则其双亲结点的编号为 i/2。 (2)如果2i>n,则结点i没有左孩子;否则其左孩子结点的编号为2i。 (3)如果2i+1>n,则结点i没有右孩子;否则其右孩子结点的编号为2i+1。
5.2.3 二叉树的基本操作与存储实现 二叉树也可以采用两种存储方式:顺序存储结构和链式存储结构。 1. 顺序存储结构 这种存储结构适用于完全二叉树。其存储形式为:用一组连续的存储单元按照完全二叉树的每个结点编号的顺序存放结点内容。下面是一棵二叉树及其相应的存储结构。
A B C D E F G H I J
A B C D E F G
A B C D
二叉树的顺序存储表示可描述为: #define MAXNODE /*二叉树的最大结点数*/ typedef elemtype SqBiTree[MAXNODE] /*0号单元存放根结点*/ SqBiTree bt; 即将bt定义为含有MAXNODE个elemtype类型元素的一维数组。
2 链式存储结构 在顺序存储结构中,利用编号表示元素的位置及元素之间孩子或双亲的关系,因此对于非完全二叉树,需要将空缺的位置用特定的符号填补,若空缺结点较多,势必造成空间利用率的下降。在这种情况下,就应该考虑使用链式存储结构。 (1)二叉链表存储 结点结构如下所示: Lchild Data Rchild
BT A A B C B ^ C D E F ^ D ^ E ^ F ^ G H ^ G ^ ^ H ^
其中,Lchild和Rchild是分别指向该结点左孩子和右孩子的指针,item是数据元素的内容。在C语言中的类型定义为:其中,Lchild和Rchild是分别指向该结点左孩子和右孩子的指针,item是数据元素的内容。在C语言中的类型定义为: struct BiTNode { elemtype data; struct BiTNode *lchild;*rchild;/*左右孩子指针*/ }BiTNode,*BiTree;
A Lchild Data Rchild Parent G H (2)三叉链表存储 这种存储结构的特点是寻找孩子结点容易,双亲比较困难。因此,若需要频繁地寻找双亲,可以给每个结点添加一个指向双亲结点的指针域,其结点结构如下所示。 B C D F E
3. 二叉树的基本操作 (1) Initiate(bt)建立一棵 (2)Create(x,lbt,rbt)生成一棵以x为根结点的数据域信息,以二叉树lbt和rbt为左子树和右子树的二叉树。 (3)InsertL(bt,x,parent)将数据域信息为x的结点插入到二叉树bt中作为结点parent的左孩子结点。如果结点parent原来有左孩子结点,则将结点parent原来的左孩子结点作为结点x的左孩子结点。
(4)InsertR(bt,x,parent)将数据域信息为x的结点插入到二叉树bt中作为结点parent的右孩子结点。如果结点parent原来有右孩子结点,则将结点parent原来的右孩子结点作为结点x的右孩子结点。(4)InsertR(bt,x,parent)将数据域信息为x的结点插入到二叉树bt中作为结点parent的右孩子结点。如果结点parent原来有右孩子结点,则将结点parent原来的右孩子结点作为结点x的右孩子结点。 (5)DeleteL(bt,parent)在二叉树bt中删除结点parent的左子树。 (6)DeleteR(bt,parent)在二叉树bt中删除结点parent的右子树。 (7)Search(bt,x)在二叉树bt中查找数据元素x。 (8)Traverse(bt)按某种方式遍历二叉树bt中的全部结点。
5.2.4 二叉树的遍历 二叉树是一种非线性的数据结构,在对它进行操作时,总是需要逐一对每个数据元素实施操作,这样就存在一个操作顺序问题,由此提出了二叉树的遍历操作。 遍历二叉树就是按某种顺序访问二叉树中的每个结点一次且仅一次的过程。 二叉树的遍历方式分为两大类: 一类按根、左子树和右子树三个部分进行访问; 另一类按层次访问。
1. 按根、左子树和右子树三部分进行遍历 遍历二叉树的顺序存在下面6种可能: TLR(根左右), TRL(根右左) LTR(左根右), RTL(右根左) LRT(左右根), RLT(右左根) 其中,TRL、RTL和RLT三种顺序在左右子树之间均是先右子树后左子树,这与人们先左后右的习惯不同,因此,往往不予采用。余下的三种顺序TLR、LTR和LRT根据根访问的位置不同分别被称为先序遍历、中序遍历和后序遍历。
A B C D E F G (1)先序遍历 若二叉树为空,则结束遍历操作;否则 访问根结点; 先序遍历左子树; 先序遍历右子树。 先序序列: ABDGCEF (2)中序遍历 若二叉树为空,则结束遍历操作;否则 中序遍历左子树; 访问根结点; 中序遍历右子树。 中序序列: DGBAECF
A B C D E F G (3)后序遍历 若二叉树为空,则结束遍历操作;否则 后序遍历左子树; 后序遍历右子树; 访问根结点。 后序序列: GDBEFCA
2. 按层次遍历二叉树 实现方法为从上层到下层,每层中从左侧到右侧依次访问每个结点。下面我们将给出一棵二叉树及其按层次顺序访问其中每个结点的遍历序列。 A B C D E F G 按层次遍历该二叉树的序列为:ABCDEFG
5.3 树的森林 5.3.1 树的存储 1. 双亲表示法 树的双亲表示法主要描述的是结点的双亲关系。 存储表示可描述为: #define MAXNODE <树中结点的最大个数> struct { elemtype data; int parent; } NodeType; NodeType t[MAXNODE]; 这种存储方法的特点是寻找结点的双亲很容易,但寻找结点的孩子比较困难。
A 序号 data parent 0 1 2 3 4 5 6 7 8 B C D E F G H I
2. 孩子链表表示法 孩子表示法主要描述的是结点的孩子关系。由于每个结点的孩子个数不定,所以利用链式存储结构更加适宜。举例: 序号 data firstchild A 0 1 2 3 4 5 6 7 8 1 2 ^ B C 3 4 5 ^ 6 ^ D E F G H I 8 ^ 7 ^
在C语言中,这种存储形式定义如下: #define MAXNODE <树中结点的最大个数> struct ChildNode { int childcode; struct ChildNode *nextchild; } struct { elemtype data; struct ChildNode *firstchild; }NodeType; NodeType t[MAXNODE];
3. 孩子兄弟表示法 这是一种常用的存储结构。其方法是这样的:在树中,每个结点除其信息域外,再增加两个分别指向该结点的第一个孩子结点和下一个兄弟结点的指针。在这种存储结构下,树中结点的存储表示可描述为: struct TreeNode { elemtype data; struct TreeNode *son; struct TreeNode *next; }NodeType;
A A ^ B C D E F G B C ^ H I ^ D E F ^ ^ G ^ ^ I ^ ^ H
5.3.2 树、森林与二叉树的转换 1.树转换成二叉树 将一棵树转换成二叉树的方法: • 树中所有相邻兄弟之间加一条连线。 • 对树中的每个结点,只保留它与第一个孩子结点之间的连线,删去它与其他孩子结点之间的连线。 • 以树的根结点为轴心,将整棵树顺时针转动一定的角度,使之结构层次分明。 A A A A B C D B B C D B C D E F E E F E F G G C G 特点:一棵树转换成二叉树后,根结点没有右孩子。 D F G
2.将森林转换成二叉树的方法: (1)将森林中的每棵树转化成相应的二叉树。 (2)第一棵二叉树不动,从第二棵二叉树开始,依次把后一棵二叉树的根结点作为前一棵二叉树根结点的右孩子,当所有二叉树连起来后,此时所得到的二叉树就是由森林转化得到的二叉树。 A A G C B C I H B D F E G D K J H E I G C A J K F K H D B I E J K F B
3.二叉树还原成树或森林 具体方法: • 若某结点是其双亲的左孩子,则把该孩子结点的右孩子,右孩子的右孩子都与该结点的双亲结点用线连起来。 • 删去原二叉树中所有的双亲结点与右孩子结点的连线。 • 整理由(1)(2)两步所得到的树或森林,使之结构层次分明。 A E B F G E A G C H I B H D F C D I
5.3.3 树和森林的遍历 A 1.树的遍历 (1)先根遍历. 结果是:ABEFCDG (2)后根遍历 结果是:EFBCGDA B C D E F G
2.森林的遍历 • 前序遍历 结果是:ABCDEFGHJIK (2)后序遍历 结果是:BADEFCJHKIG A G C I H B D F E J K
5.4 哈夫曼树的基本概念 哈夫曼树的基本概念 最优二叉树也称哈夫曼树是指对于一组带有确定权植的叶子结点,构造的具有最小带权路径长度的二叉树. 设二叉树具有n个带权值的叶子结点,那么从根结点到各个叶子结点的路径长度与相应结点权值的乘积之和叫做二叉树的带权路径长度,记为: 如:图所示一个带权二叉树.WPL=2*2+4*2+5*2+3*2=28 2 4 5 3
给定一组具有确定权值的叶子结点,可以构造出不同的带权二叉树.给定一组具有确定权值的叶子结点,可以构造出不同的带权二叉树. (1)WPL=1*2+3*2+5*2+5*2=28 (2)WPL=1*3+3*3+5*2+5*1=27 (3)WPL=1*2+3*3+5*#+5*1=31 5 5 5 1 1 3 1 3 5 5 3 5
构造哈夫曼树的过程: (1)将给定的n个权值{w1,w2,...,wn}作为n个根结点的权值构造一个具有n棵二叉树的森林{T1,T2,...,Tn},其中每棵二叉树只有一个根结点; (2)在森林中选取两棵根结点权值最小的二叉树作为左右子树构造一棵新二叉树,新二叉树的根结点权值为这两棵树根的权值之和; (3)在森林中,将上面选择的这两棵根权值最小的二叉树从森林中删除,并将刚刚新构造的二叉树加入到森林中; (4)重复上面(2)和(3),直到森林中只有一棵二叉树为止。这棵二叉树就是哈夫曼树。
5 5 5 5 3 1 3 1 5 5 3 1 5 5 1 3
5 6 2 9 7 7 5 2 例如: 已知权值 W={ 5, 6, 2, 9, 7 } 6 9 7 7 5 2 9 13 6 7
7 13 5 2 6 7 13 6 7 7 5 2 9 29 16 哈夫曼树 9 可以证明哈夫曼树是最优树 wpl=6×2+7×2+5×3+2×3+9×2=65
34 34 20 20 13 7 13 14 14 6 6 7 7 7 7 7 7 7 5 5 2 2 例如: 已知权值 W={ 5, 6, 2, 7, 7,7 } 5 6 2 7 7 7 WPL = 6×3+7×4+7×2+14×2 =18+7×9=81 WPL = 6×3+7×3+7×3+14×2 =18+7×9=81