目录
1,二叉树存在问题分析
虽然我们说二叉排序树,平衡二叉树的查找操作效率较高,但是也存在问题, 请看下面的二叉树。
- 二叉树需要加载到内存的,如果二叉树的节点少,没有什么问题,但是如果二叉树的节点很多(比如1亿), 就存在如下问题:
- 问题1:在构建二叉树时,需要多次进行i/o操作(海量数据存在数据库或文件中),节点海量,构建二叉树时,速度有影响。
- 问题2:节点海量,也会造成二叉树的高度很大,会降低操作速度。
- 二叉树每一个节点只有两个子树,存储的数据量也有限,那我们就像,能否通过提高每一个节点的分支数量来提高我们信息的存储量,这个就是我们今天要讲解的多叉树。
2,多叉树B树的介绍
B树,又叫做多路平衡查找树,B树中所有节点的孩子个数的最大值称为B树的阶数,通常我们使用m标示,一棵m阶的B树为空树,或者为满足如下特性的m阶B数:
- 树中每一个节点最多有m棵子树,也就是最多有m-1个关键字。
- 如果根节点不是终端节点,那么根节点至少有两棵子树。
- 除了根节点之外所有的非叶子节点至少有
棵子树。(符号表示向上取整),也就是至少含有
-1个关键字。
- 所有的非叶子节点的结构如下:
其中表示的是关键字,并且满足
,
为指向子树根节点的指针,并且指针
所指向的子树中所有节点中的关键字均小于关键字
,
所指的子树中所有的关键字均大于关键字
,
为节点中关键字的个数。
- 所有的叶节点均出现在同一层次上面,并且不带信息(可以认为是外部节点或者是类似于折半查找判定树中查找失败的节点,实际上这些节点是不存在的,指向这些节点的指针是空的)。
- B树是所有节点的平衡因子均为0的多路平衡查找树。
定义看起来很枯燥,下面我们就来看看真实的B树长的什么样子
下面我们根据性质来分析一下这一棵3阶B树:
- 孩子节点的个数等于该节点中关键字的个数+1。
- 如果根节点中没有关键字就没有子树,此时B树是空树,如果根节点中有关键字,那么其子树必然大于等于两棵,本树中有3个子树,也就是子树个数等于关键字个数+1。
- 除了根节点之外所有非终端节点至少有
=2棵子树,也就是说至少有2-1个关键字,最多有3棵子树,也就是最多有2个关键字。
- 节点中关键字从左到右依次递增有序,关键字两侧均有指向子树的指针,左边的指针所指的子树中所有关键字均小于该关键字,右边指针所指的子树中所有关键字均大于该关键字。
- 所有叶子节点均在第4层,也就是查找失败的位置。
这里需要注意的是,我们下面要讲解的2-3树和2-3-4树都是B树的一种特殊的形式,2-3 树是 3 阶 B 树,2-3-4 树是 4 阶 B 树,不同的是,2-3树中每一个节点中要么有一个值包含两个孩子或者没有孩子,或者一个节点中包含两个数值,三个孩子或者是没有孩子,不能只有一个或者两个孩子,这是与普通B树不同的地方,2-3-4树也是同样的道理。
2.1,2-3树和2-3-4树的介绍
好了,上面了解了B树,下面看2-3树和2-3-4树就很好理解了。
我们先来看看2-3树的定义:
- 2-3树是这样的一棵多路查找树:其中的每一个节点都具有两个孩子(我们称它为 2 节点)或三个孩子(我们称它为 3 节点)。
-
一个 2 节点包含一个元素和两个孩子(或没有孩子),且与二叉排序树类似,左子树包含的元素小于该元素,右子树包含的元素大于该元素。不过,与二叉排序树不同的是,这个 2 节点要么没有孩子,要么就有两个,不能只有一个孩子。与普通B树的区别。
-
一个 3 节点包含一大一小两个元素和三个孩子(或没有孩子),一个 3 节点要么没有孩子,要么具有 3 个孩子。如果某个 3 节点有孩子的话,左子树包含小于较小元素的元素,右子树包含大于较大元素的元素,中间子树包含介于两元素之间的元素。
-
并且 2-3 树种所有的叶子都在同一层次上。如下图所示就是一颗有效的 2-3 树。
-
下面是一棵2-3树,注意:每个节点中要么有2个孩子或者是3个孩子(两个孩子的节点对应有一个元素值,三个孩子节点的对应有2个元素值),里面是不存在只有一个孩子的节点的。
- 事实上,2-3 树复杂的地方就在于新节点的插入和已有节点的删除。毕竟每个节点可能是 2 节点也可能是 3 节点,要保证所有叶子都在同一层次,是需要一番复杂操作的。
接下来我们再来看看2-3-4树的定义:
- 有了 2-3 树的讲解,2-3-4 树就很好理解了,它其实就是 2-3 树的概念扩展,包括了 4 节点的使用(也即是节点中有3个元素的那种情况)。
- 一个 4 节点包含小中大三个元素和四个孩子(或没有孩子),一个 4 节点要么没有孩子,要么具有 4 个孩子。
- 如果某个 4 节点有孩子的话,左子树包含小于最小元素的元素;第二子树包含大于最小元素,小于第二元素的元素;第三子树包含大于第二元素,小于最大元素的元素;右子树包含大于最大元素的元素
2.2,B树的查找
在B 树上进行查找与二叉查找树很相似,只是每个结点都是多个关键字的有序表,在每个结点上所做的不是两路分支决定,而是根据该结点的子树所做的多路分支决定。
- B 树的查找包含两个基本操作:
- 在B 树中找结点,
- 在结点内找关键字,
- 由于B 树常存存储在磁盘上,因此前一个查找操作是在磁盘上进行的,而后一个查找操作是在内存中进行的, 即在找到目标结点后,先将结点信息读入内存,然后在结点内采用顺序查找法或折半查找法。
- 在B 树上查找到某个结点后, 先在有序表中进行查找,若找到则查找成功,否则按照对应的指针信息到所指的子树中去查找。
- 例如我们在上面的3阶树中查找元素90,先和根节点中的元素比较,90>50,所以就找到节点50的右孩子,也就是节点80,但是90>80,于是就接着和80节点的右子树比较,80的右子树上面有两个节点,90和这两个元素逐个比较,发现查找到90就返回。
2.3,B树的创建和插入
与二叉查找树的插入操作相比, B 树的插入操作要复杂得多。在二又查找树中,仅需查找到需插入的终端结点的位置。但是,在B 树中找到插入的位置后,并不能简单地将其添加到终端结点中,因为此时可能会导致整棵树不再满足B 树定义中的要求。将关键字key 插入B 树的过程如下:
- 定位。利用前述的B 树查找算法,找出插入该关键字的最低层中的某个非叶结点(在B树中查找key 时,会找到表示查找失败的叶结点,这样就确定了最底层非日f结点的插入位置。注意:插入位置一定是最低层中的某个非叶结点)。
- 插入。在B 树中每个非失败结点的关键字个数都在区间[m/2l -1, m - 1 ] 内。插入后的结点关键于个数小于m , 可以直接插入,插入后检查被插入结点内关键宇的个数。当插入后的结点关键字个数大于m-1 时,必须对结点进行分裂。
- 分裂的方法是:取一个新结点,在插入key 后的原结点,从中间位置(
) 将其中的关键宇分为两部分,左部分包含的关键字放在原结点中, 右部分包含的关键字放到新结点中,中间位置(
) 的结点插入原结点的父结点。若此时导致其父结点的关键宇个数也超过了上限,则继续进行这种分裂操作,直至这个过程传到根结点为止,进而导致B 树高度增1。
- 分裂的方法是:取一个新结点,在插入key 后的原结点,从中间位置(
下面我们来创建一棵B树并逐个插入节点:以(20,30,50,52,60,68,70)序列来创建一颗3阶B树。因为M=3,所以除了根节点外,非叶节点中元素个数是1-2。
- 第一步:首先插入20.30 ,结点内关键字个数不超过Im/2l= 2 ,不会引起分裂。
- 第二步:插入50,由于在插入元素50之后,节点内元素超过2个,所以需要进行分裂操作,把
处的元素作为父节点分裂出去。
- 第三步:插入元素52
- 第四步:插入60 ,插入50, 52 所在的结点, 引起分裂,但上升到父结点中,不会引起父结点的分裂。
- 第五步:插入68 , 插入60 所在的结点,不会引起分裂
- 第六步:插入70 ,插入60 , 68 所在的结点, 引起分裂, 68 上升为新的父结点, 68 上升到30 . 52 所在的结点后,会继续引起该结点的分裂,故52 上升为新的根结点。最后得到的B 树如下图所示。
可以看到,B树在创建和插入元素的过程中,是不断向上长高的,
2.4,B树的删除
B 树中的删除操作与插入操作类似,但要稍微复杂一些,即要使得删除后的结点中的关键字个数>=-1,因此将涉及结点的"合并"问题。
- 当被删关键子k 不在终端结点(最低层非叶结点) 中时可用k 的前驱(或后继) k'来替 代k ,然后在相应的结点中删除k',关键字k 必定落在某个终端结点中, 则转换成被删除的关键字在终端节点中的情况,
- 下面的4 阶B 树中, 删除关键宇80 ,用其前驱78 替代,然后在终端结点中删除78, 因此只需讨论删除终端结点中关键字的情形。
- 当被删关键字在终端结点(最低层非叶结点)中时,有下列三种情况:
- 直接删除关键字。若被删除关键字所在结点的关键宇个数=
-1, 表明删除该关键字后仍满足B 树的定义,则直接删去该关键字。
- 兄弟够借。若被删除关键字所在结点删除前的关键宇个数=
- 1,且与此结点相邻的右(或左)兄弟结点的关键宇个数>
, 则需要调整该结点、右(或左)兄弟结点
及其双亲结点(父子换位法),以达到新的平衡。下图中删除4 阶B 树的关键字65 ,右兄弟关键字个数>==2 ,将71 取代原65 的位置,将74 调整到71 的位置。
- 直接删除关键字。若被删除关键字所在结点的关键宇个数=
- 兄弟不够借。若被删除关键字所在结点删除前的关键宇个数=
- 1,且此时与该结点相邻的左、右兄弟结点的关键字个数均=
-1,则将关键宇删除后与左(或右)兄弟结点及双亲结点中的关键字进行合并。下图中删除4 阶B 树的关键宇5 ,它及其右兄弟结点的关键字个数=
- 1 = 1,故在5 删除后将60 合并到65 结点中。
在合并过程中,双亲结点中的关键宇个数会减1 。若其双亲结点是根结点且关键宇个数减少至o (根结点关键宇个数为1 时, 有2 棵子树),则直接将根结点删除,合并后的新结点成为根;若双亲结点不是根结点,且关键宇个数减少到- 2 , 则又要与它自己的兄弟结点进行调整或合并操作,并重复上述步骤,直至符合B 树的要求为止。
下面我们对一棵三阶B树进行插入和删除操作,三阶B树如下:
第一步:插入90,90插入100所在的节点,不会引起分裂。
- 第二步:插入25,25插入8,20所在的节点,元素个数大于2,所以会引起分裂,把中间位置节点上升到父节点位置。
- 第三步:插入45,将45 插入35, 40 所在的结点,引起分裂, 中间元素40 上升到父结点(20, 30 所在的结点)中,引起父结点分裂,中间元素30 上升到父结点(50 所在的结点)中,两次分裂后的B 树如下图所示。
- 第四步:删除60,删除60 后,其所在的结点元素为空,从而导致借用右兄弟结点的元素,调整后的B 树如下图所示。(这种情况是其兄弟够借的情况)
- 第五步:删除80,删除80 后,导致80 所在结点的父结点与其右兄弟结点合井,这时父结点元素个数为0 ,再次对父结点进行调整。将50 与40 合并成一个新结点, 则90,100 所在结点为这个结点的子结点。从而构造的B 树如下图所示。注意,这次调整的过程实际上包含多次调整过程, 希望读者对照考点讲解中的删除过程仔细思考。
至此,我们的删除操作也基本说完了。
3,B+树
B+树是应数据库所需而出现的一种B 树的变形树。一棵m 阶的B十树需满足下列条件:
- 每个分支结点最多有m 棵子树(孩子结点)。
- 非叶根结点至少有两棵子树,其他每个分支结点至少有
棵子树。
- 结点的子树个数与关键宇个数相等。
- 所有叶结点包含全部关键字及指向相应记录的指针,叶结点中将关键宇按大小顺序排列,并且相邻叶结点按大小顺序相互链接起来。
- 所有分支结点(可视为索引的索引)中仅包含它的各个子结点( 即下一级的索引块)中关键字的最大值及指向其子结点的指针。
下图为一棵4 阶B十树。可以看出,分支结点的某个关键字是其子树中最大关键字的副本。通常在B+树中有两个头指针:一个指向根结点,另一个指向关键子最小的叶结点。因此,可以对B+树进行两种查找运算:一种是从最小关键字开始的顺序查找,另一种是从根结点开始的多路查找。
- B+树的查找、插入和删除操作和B 树的基本类似。只是在查找过程中, 非叶结点上的关键宇值等于给定值时并不终止,而是继续向下查找, 直到叶结点上的该关键字为止。所以,在B+树中查找时,无论查找成功与否,每次查找都是一条从根结点到叶结点的路径。
3.1,m 阶的B+树与m阶的B树的主要差异
- 在B+树中, 具有n 个关键字的结点只含有n 棵子树,即每个关键于对应一棵子树;而在B 树中,具有n 个关键字的结点含有n 十l 棵子树。
- 在B+树中,每个结点(非根内部结点)的关键宇个数n 的范围是
<=n <=m (根结点:1<=n <=m) ; 在B 树中,每个结点(非根内部结点〉的关键字个数n 的范围是
-1<=n<=m - 1 (根结点: 1 <=n<=m 一1)。
- 在B+树中, 叶结点包含信息,所有非叶结点f夹起索引作用,非叶结点中的每个索引项只含有对应子树的最大关键字和指向该子树的指针,不含有该关键字对应记录的存储地址。
- 在B+树中, 叶结点包含了全部关键字, 即在非叶结点中出现的关键字也会出现在叶结于是中;而在B树中,叶结点包含的灰飞建字和其他结点包含的关键宇是不重复的。
参考资料:
[1] https://www.jianshu.com/p/174a815b1495