算法预备军(4)~树的概念

来源:互联网 发布:拍淘宝衣服的模特要求 编辑:程序博客网 时间:2024/05/16 11:24

说明:算法预备军系列内容均为个人的学习笔记,主要是指数据结构方面的,后面在继续学习的过程中会陆续分享相关内容.数据结构这块主要学习来源为<大话数据结构这本书>,大家不喜欢摘录的,可以自行看书.

树的基础

树的定义

定义

树(Tree)是n(n>=0)个结点的有限集。n=0时称为空树。在任意一棵非空树中:
(1)有且仅有一个特定的 称为根(Root)的结点;
(2)当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1,T2,…….,Tn,
其中每一个集合本身又是一棵树,并且称为根的子树(SubTree)。

结点分类

树的结点包含一个数据元素及若干指向其子树的分支。
结点拥有的子树数称为结点的度。
度为0的结点称为叶结点或终端结点。度不为0的结点称为非终端结点或者分支结点。
除根结点之外,分支结点也称为内部结点。树的度是树内各结点的度的最大值。

结点间关系

结点的子树的根称为该结点的孩子,相应地,该结点称为孩子的双亲。
结点的祖先是从根到该结点所经分支上的所有结点。
以某结点为根的子树中的任一结点都称为该结点的子孙。

树的其他相关概念

结点的层次从根开始定义起,根为第一层,根的孩子为第二层。
双亲在同一层的结点互为堂兄弟。
树中结点的最大层次称为树的深度或高度。
如果将树中结点的各子树看成从左至右是有次序的,不能互换的,则称该树为有序树,否则称为无序树。
森林是m(m>=0)棵互不相交的树的集合。对树中每个结点而言,其子树的集合即为森林。

树的存储结构的表示

(1)双亲表示法(2)孩子表示法(3)孩子兄弟表示法

二叉树

定义

二叉树是n(n>=0)个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点和
两棵互不相交的、分别称为根结点的左子树和右子树的二叉树组成。

特点

(1)每个结点最多有两棵子树,所以二叉树中不存在度大于2的结点。注意不是只有两棵子树,而是最多有。
没有子树或者有一棵子树都是可以的。
(2)左子树和右子树是有顺序的,次序不能任意颠倒。
(3)即使树中某结点只有一棵子树,也要区分它是左子树还是右子树。

二叉树的五种基本形态

(1)空二叉树(2)只有一个根结点(3)根结点只有左子树(4)根结点只有右子树(5)根结点既有左子树又有右子树

特殊二叉树

(1)斜树:所有的结点都只有左子树的二叉树叫左斜树。所有结点都是只有右子树的二叉树叫右斜树。这两者统称为斜树。

(2)满二叉树:在一棵二叉树中,如果所有分支结点都存在左子树和右子树,并且所有叶子都在同一层上,这样的二叉树称为满二叉树。

满二叉树的特点:叶子只能出现在最下一层;非叶子结点的度一定是2;在同样深度的二叉树中,满二叉树的结点个数最多,叶子数最多。

(3)完全二叉树:对一棵具有n个结点的二叉树按层序编号,如果编号为i(1<=i<=n)的结点与同样深度的满二叉树中编号为i的结点在二叉树中位置完全相同,则这棵二叉树称为完全二叉树。首先从字面上要区分,“完全”和“满”的差异,满二叉树一定是一棵完全二叉树,但完全二叉树不一定是满的。其次,完全二叉树的所有结点与同样深度的满二叉树,它们按层序编号相同的结点,是一一对应的。

完全二叉树的特点:
(1)叶子结点只能出现在最下两层。
(2)最下层的叶子一定集中在左部连续位置
(3)倒数二层,若有叶子结点,一定都在右部连续位置
(4)如果结点度为1,则该结点只有左孩子,即不存在只有右子树的情况
(5)同样结点数的二叉树,完全二叉树的深度最小。

二叉树的性质

(1)在二叉树的第i层上至多有2^(i-1)个结点(i>=1)
(2)深度为k的二叉树至多有2^k -1个结点(k>=1)
(3)对任何一棵二叉树T,如果其终端结点数为n0,度为2的结点数为n2,则n0=n2 +1 。
(4)具有n个结点的完全二叉树的深度为[log2n]+1。
(5)如果对一棵有n个结点的完全二叉树(其深度为[long2n]+1)的结点按层序编号(从第一层到第[long2n]+1层, 每层从左到右),对任一结点i(1<=i<=n)有:
1.如果i=1,则结点i是二叉树的根,无双亲;如果i>1,则其双亲是结点[i/2].
2.如果2i>n,则结点i无左孩子(结点i为叶子结点);否则其左孩子是结点2i
3.如果2i+1>n,则结点i无右孩子;否则其右孩子是结点2i+1.

二叉树的存储结构

二叉树的顺序存储结构:将二叉树当作完全二叉树来存储,不存在的结点用/\来表示。

二叉链表:二叉树每个结点最多有两个孩子,所以为它设计一个数据域和两个指针域是比较自然的想法,我们称这样的链表叫做二叉链表。如果再增加一个指向其双亲的指针域,就称为三叉链表。

遍历二叉树

二叉树的遍历是指从根结点出发,按照某种次序依次访问二叉树中所有结点,使得每个结点被访问一次且被访问一次。

二叉树的主要遍历方式:(1)前序遍历(2)中序遍历(3)后序遍历(4)层序遍历。

前序遍历:规则是若二叉树为空,则空操作返回,否则先访问根结点,然后前序遍历 左子树,再前序遍历右子树。

中序遍历:规则是若树为空,则空操作返回,否则从根结点开始(注意并不是先访问根结点),中序遍历根结点的左子树,然后是访问根结点,最后中序遍历右子树。

后序遍历:规则是若树为空,则空操作返回,否则从左到右先叶子后结点的方式遍历访问左右子树,最后是访问根结点。

层序遍历:规则是若树为空,则空操作返回,否则从树的第一层,也就是根结点开始访问,从上而下逐层遍历,在同一层中,按从左到右的顺序对结点逐个访问。

二叉树遍历的性质:
(1)已知前序遍历序列和中序遍历序列,可以唯一确定一棵二叉树
(2)已知后序遍历序列和中序遍历序列,可以唯一确定一棵二叉树
(3)但是已知前序和后序遍历,是不能确定一棵二叉树的。

线索二叉树

在二叉链表的结构中我们有很多的空指针域,如果我们把空指针域利用起来,存放指向某种遍历结果序列的前驱和后继的指针,这样就有效地利用了内存空间。我们把这种指向前驱和后继的指针称为线索,加上线索的二叉链表称为线索链表,相应的二叉树就称为线索二叉树。

其实线索二叉树,等于是把一棵二叉树转变成了一个双向链表,这样对我们的插入删除结点,查找某个结点都带来了方便。所以我们对二叉树以某种次序遍历使其变为线索二叉树的过程称做是线索化。

在二叉链表的每个结点上增加两个标志域ltag和rtag。用来标识指针域是指向前驱(后继)还是左孩子(右孩子)。如果所用的二叉树需经常遍历或查找结点时需要某种遍历序列中的前驱和后继,那么采用线索二叉链表的存储结构就是非常不错的选择。

树、森林与二叉树的转换

(1)将树转换为二叉树的步骤如下:

1.加线。在所有兄弟结点之间加一条线
2.去线。对树中每个结点,只保留它与第一个孩子结点的连线,删除它与其他孩子结点之间的连线。
3.层次调整。以树的根结点为轴心,将整棵树顺时针旋转一定的角度,使之结构层次分明。注意第
一个孩子是二叉树结点的左孩子,兄弟转换过来的孩子是结点的右孩子。

(2)森林转换为二叉树

森林是由若干棵树组成的,所以完全可以理解为,森林中的每一棵树都是兄弟,可以按照兄弟的处理
方法来操作。步骤如下:
1.把每个树转换为二叉树。
2.第一棵二叉树不动,从第二棵二叉树开始,依次把后一棵二叉树的根结点作为前一棵二叉树根结点
的右孩子,用线连接起来。当所有的二叉树连接起来后就得到了由森林转换来的二叉树。
注意:有些树看似是二叉树其实不是二叉树,因为没有严格区分左右子树。

(3)二叉树转换为树

二叉树转换为树是树转换为二叉树的逆过程,也就是反过来做而已。步骤如下:
1.加线。若某结点的左孩子结点存在,则将这个左孩子的右孩子结点、右孩子的右孩子结点、
 右孩子的右孩子的右孩子结点…哈,反正就是左孩子的n个右孩子结点都作为此结点的孩子。
将该结点与这些右孩子结点用线连接起来。
2.去线。删除原二叉树中所有结点与其右孩子结点的连线。
3.层次调整。使之结构层次分明。

(4)二叉树转换为森林

判断一棵二叉树能够转换成一棵树还是森林,标准很简单,那就是只要看这棵二叉树的根结点有没有右
孩子,有就是森林,没有就是一棵树。那么如果是转换成森林,步骤如下:
1.从根结点开始,若右孩子存在,则把与右孩子结点连线删除,再查看分离后的二叉树,若右孩子存在,则连线删除….,直到所有右孩子连线都删除为止,得到分离的二叉树。
2.再将每棵分离后的二叉树转换为树即可。

树与森林的遍历

树的遍历分为两种方式:

1.一种是先根遍历树,即先访问树的根结点,然后依次先根遍历根的每棵子树。
2.另一种是后根遍历,即先依次后根遍历每棵子树,然后再访问根结点。

森林的遍历也分为两种方式:

1.前序遍历:先访问森林中第一棵树的根结点,然后再依次先根遍历根的每棵子树,再依次用同样的方式遍历除去第一棵树的剩余树构成的森林。

2.后序遍历:是先访问森林中第一棵树,后根遍历的方式遍历每棵子树,然后再访问根结点,再依次同样方式遍历除去第一棵树的剩余树构成的森林。
注意:森林的前序遍历和二叉树的前序遍历结果相同,森林的后序遍历和二叉树的中序遍历结果相同。

赫夫曼树

路径长度:从树中一个结点到另一个结点之间的分支构成两个结点之间的路径,路径上的分支数目称做路径长度。

树的路径长度:树的路径长度就是从树根到每一结点的路径长度之和。

赫夫曼树:如果考虑到带权的结点,结点的带权的路径长度为从该结点到树根之间的路径长度与结点上权的乘积。树的带权路径长度为树中所有叶子结点的带权路径长度之和。假设有n个权值{w1,w2,…,wn},构造一棵有n个叶子结点的二叉树,每个叶子结点带权Wk,每个叶子的路径长度为lk,我们通常记作,则其中带权路径长度WPL最小的二叉树称做赫夫曼树。也有不少书中也称为最优二叉树。

构造赫夫曼树的赫夫曼算法描述:

1.根据给定的n个权值{w1,w2,…,wn}构成n棵二叉树的集合F={T1,T2,…,Tn},其中每棵二叉树Ti中只有一个带权为Wi根结点,其左右子树均为空。
2.在F中选取两棵根结点的权值最小的树作为左右子树构造一棵新的二叉树,且置新的二叉树的根结点的权值为其左右子树上根结点的权值之和。
3.在F中删除这两棵树,同时将新得到的二叉树加入F中。
4.重复2和3步骤,直到F只含一棵树为止。这棵树便是赫夫曼树。

赫夫曼编码

前缀编码:若要设计长短不等的编码,则必须是任一字符的编码都不是另一个字符的编码的前缀,这种编码称做前缀编码。

赫夫曼编码:一般地,设需要编码的字符集为{d1,d2,…,dn},各个字符在电文中出现的次数或频率集合为{w1,w2,..,wn},以d1,d2,…,dn作为叶子结点,以w1,w2,…,wn作为相应叶子结点的权值来构造一棵赫夫曼树。规定赫夫曼树的左分支代表0,右分支代表1,则从根结点到叶子结点所经过的路径分支组成的0和1的序列便为该结点对应字符的编码,这就是赫夫曼编码。

二叉排序树(二叉查找树)

二叉排序树:二叉排序树又称为二叉查找树。它或者是一棵空树,或者是具有下列性质的二叉树:
1.若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值。
2.若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值。
3.它的左右子树也分别为二叉排序树。

构造一棵二叉排序树的目的,其实并不是为了排序,而是为了提高查找和插入删除关键字的速度。不管怎么说,在一个有序数据集上的查找,速度总是要快于无序的数据集的,而二叉排序树这种非线性的结构,也有利于插入和删除的实现。

平衡二叉树(AVL树)

平衡二叉树:平衡二叉树是一种二叉排序树,其中每一个节点的左子树和右子树的高度差至多等于1。

平衡二叉树是一种高度平衡的二叉排序树。意思是说,要么它是一棵空树,要么它的左子树和右子树都是平衡二叉树,且左子树和右子树的深度之差的绝对值不超过1。我们将二叉树上结点的左子树深度减去右子树深度的值称为平衡因子BF,那么平衡二叉树上所有结点的平衡因子只可能是-1,0和1。只要二叉树上有一个结点的平衡因子的绝对值大于1,则该二叉树就是不平衡的。

最小不平衡子树:距离插入结点最近的,且平衡因子的绝对值大于1的结点为根的子树,我们称为最小不平衡子树。

平衡二叉树实现原理

平衡二叉树构建的基本思想就是在构建二叉排序树的过程中,每当插入一个结点时,先检查是否因插入而破坏了树的平衡性,若是,则找出最小不平衡子树。在保持二叉排序树特性的前提下,调整最小不平衡子树中各结点之间的链接关系,进行相应的旋转,使之成为新的平衡子树。

关于构建平衡二叉树过程中的旋转的说明:

当最小不平衡子树根结点的平衡因子BF是大于1时,就右旋,小于-1时就左旋。插入结点后,最小不平衡子树的BF与它的子树的BF符号相反时,就需要对结点先进行一次旋转以使得符号相同后,再反向旋转一次才能够完成平衡操作。

多路查找树(B树)

我们之前谈的树,都是一个结点可以有多个孩子,但是它自身只存储一个元素。二叉树限制更多,结点最多只能有两个孩子。一个结点只能存储一个元素,在元素非常多的时候,就使得要么树的度非常大(结点拥有子树的个数的最大值),要么树的高度非常大,甚至两者都必须足够大才行。这就使得内存存取外存次数非常多,这显然成了时间效率上的瓶颈,这迫使我们要打破每一个结点只存储一个元素的限制,为此引入了多路查找树的概念。

多路查找树:多路查找树的每一个结点的孩子数可以多于两个,且每一个结点处可以存储多个元素。由于它是查找树,所有元素之间存在某种特定的排序关系。在这里,每一个结点可以存储多少个元素,以及它的孩子数的多少是非常关键的,为此,我们讲解它的4种特殊形式:2-3树,2-3-4树,B树和B+树。

2-3树

2-3树是这样的一棵多路查找树:其中的每一个结点都具有两个孩子(我们称它为2结点)或三个孩子(我们称它为3结点)。

一个2结点包含一个元素和两个孩子(或没有孩子),且与二叉排序树类似,左子树包含的元素小于该元素,右子树包含的元素大于该元素。不过,与二叉排序树不同的是,这个2结点要么没有孩子,要有就有两个,不能只有一个孩子。

一个3结点包含一小一大两个元素和三个孩子(或没有孩子),一个3结点要么没有孩子,要么具有3个孩子。如果某个3个结点有孩子的话,左子树包含小于较小元素的元素,右子树包含大于较大元素的元素,中间子树包含介于两元素之间的元素。

注意:2-3树中所有的叶子都在同一层次上。

在对2-3树的插入与删除操作的过程中,要牢记2-3树中所有的叶子都在同一层次上。

2-3-4树

2-3-4树:2-3-4树其实就是2-3树的概念扩展,包括了4结点的使用。

一个4结点包含小中大三个元素和四个孩子(或没有孩子),一个4结点要么没有孩子,要么具有4个孩子。如果某个4结点有孩子的话,左子树包含小于最小元素的元素;第二子树包含大于最小元素,小于第二元素的元素;第三子树包含大第二元素,小于最大元素的元素; 右子树包含大于最大元素的元素。

B树

B树是一种平衡的多路查找树,2-3树和2-3-4树都是B树的特例。结点最大的孩子数目称为B树的阶。因此,2-3树是3阶B树,2-3-4树是4阶B树。

一个m阶的B树具有如下属性:
1.如果根结点不是叶结点,则其至少有两棵子树。
2.每一个非根的分支结点都有k-1个元素和k个孩子,其中[m/2]<=k<=m。每一个叶子结点n都有k-1个元素,其中[m/2]<=k<=m.
3.所有叶子结点都位于同一层次
4.所有分支结点包含下列信息数据(n,A0,K1,A1,K2,A2,…,Kn,An),其中:ki(i=1,2,…,n)为关键字,且Ki < k i+1=(i=1,2,…,n-1);Ai(i=0,2,..,n)w为指向子树根结点的指针,且指针A= i-1所指向子树中所有结点的关键字均小于Ki(i=1,2,…,n),An所指子树中所有结点的关键字均大于Kn,n([m/2]-1<=n<=m-1)为关键字的个数(或n+1为子树的个数)。

在B树上查找的过程是一个顺指针查找结点和在结点中查找关键字的交叉过程。B树的数据结构就是为内外存的数据交互准备的。

B+树

B+树是应文件系统所需而出的一种B树的变形树。在B树中,每一个元素在该树中只出现一次,有可能在叶子结点上,也有可能在分支结点上。而在B+树中,出现在分支结点中的元素会被当作它们在该分支结点位置的中序后继者(叶子结点)中再次列出。另外,每一个叶子结点都会保存一个指向后一叶子结点的指针。

一棵m阶的B+树和m阶的B树的差异在于:
1.有n棵子树的结点中包含有n个关键字
2.所有的叶子结点包含全部关键字的信息,及指向含这些关键字记录的指针,叶子结点本身依关键字的大小自小而大顺序链接
3.所有分支结点可以看成是索引,结点中仅含有其子树中的最大(或最小)关键字。

如果我们是需要从最小关键字进行从小到大的顺序查找,我们就可以从最左侧的叶子结点出发,不经过分支结点,而是延着指向下一叶子的指针就可遍历所有的关键字。

红黑树

红黑树是一种自平衡二叉查找树,红黑树和AVL树类似,都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能。它虽然是复杂的,但它的最坏情况运行时间也是非常良好的,并且在实践中是高效的;它可以在O(log n)时间内做查找,插入和删除,这里的n是树中元素的数目。

性质

红黑树是每个结点都带有颜色属性的二叉查找树,颜色为红色或黑色。在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求:
1.结点是红色或者黑色
2.根是黑色
3.所有叶子都是黑色(叶子是NIL结点)
4.每个红色结点必须有两个黑色的子结点(从每个叶子到根的所有路径上不能有两个连续的红色结点)
5.从任一结点到其每个叶子的所有简单路径(不包含重复的点的路径)都包含相同数目的黑色结点。
image

原创粉丝点击