查找算法 | B-树详细分析

什么是 B-树?

1970年,R.Bayer和E.mccreight提出了一种适用于外查找的树,它是一种平衡的多叉树(或多路查找树),称为 B树

B树,有时又写为B-树B_树(其中的“-”或者“_”只是连字符,并不读作“B减树”),一颗 m 阶的 B树是一棵平衡的 m 路搜索树。它或者是空树,或者是满足下列性质的树:

  1. 若根结点不是叶子结点(那种情况只能是整个树就一个根节点),则至少有两棵子树;
  2. 每个非根节点所包含的关键字个数 n 满足:⌈m/2⌉ - 1 <= n <= m - 1(向上取整);
  3. 除根结点以外的所有结点(不包括叶子结点)的度数正好是关键字总数加1,故内部子树个数 k 满足:⌈m/2⌈ <= k <= m
  4. 所有的叶子结点都位于同一层。实际上这些结点都不存在,指向这些结点的指针都为 NULL;

在 B-树中,所有的非终端结点中包含下列信息数据:(n,A0,K1,A1,K2,A2,…,Kn,An),n 表示结点中包含的关键字的个数,取值范围是:⌈m/2⌉-1≤ n ≤m-1。Ki (i 从 1 到 n)为关键字,且 Ki < Ki+1 ;Ai 代表指向子树根结点的指针,且指针 Ai-1 所指的子树中所有结点的关键字都小于 Ki,An 所指子树中所有的结点的关键字都大于 Kn。

《查找算法 | B-树详细分析》 图 1 结点结构

如图 1 所示,当前结点中有 4 个关键字,之间的关系为:K1<K2<k3<K4。同时对于 A0 指针指向的子树中的所有关键字来说,其值都要比 K1 小;而 A1 指向的子树中的所有的关键字的值,都比 K1 大,但是都要比 K2 小。比如下面的树:一棵 4 阶的 B-树,这棵树的深度为 4 :
《查找算法 | B-树详细分析》 图 2 深度为 4 的B-树

B-树的查找

在使用 B-树 进行查找操作时,例如在如图 2 所示的 B树 中查找关键字 47 的过程为:

  1. 从整棵树的根结点开始,由于根结点只有一个关键字 35,且 35 < 47 ,所以如果 47 存在于这棵树中,肯定位于 A1 指针指向的右子树中;
  2. 然后顺着指针找到存有关键字 43 和 78 的结点,由于 43 < 47 < 78,所以如果 47 存在,肯定位于 A1 所指的子树中;
  3. 然后找到存有 47、53 和 64 三个关键字的结点,最终找到 47 ,查找操作结束;

以图 2 中的 B-树为例,若查找到深度为 3 的结点还没结束,则会进入叶子结点,但是由于叶子结点本身不存储任何信息,全部为 NULL,所以查找失败。

B-树中插入关键字(构建B-树)

B-树也是从空树开始,通过不断地插入新的数据元素构建的。但是 B-树构建的过程同二叉排序树和平衡二叉树不同,B-树在插入新的数据元素时并不是每次都向树中插入新的结点。

因为对于 m 阶的 B-树来说,在定义中规定所有的非叶子结点(叶子结点其关键字个数为 0)中包含关键字的个数的范围是⌈m/2⌉ - 1 <= n <= m - 1,所以在插入新的数据元素时,首先向最底层的某个非叶子结点中添加,有下面两种情况:

  1. 如果该结点中的关键字个数没有超过 m-1,则直接插入成功;
  2. 如果该节点在添加完关键字之后(排好序),关键字数已经达到了m,该节点将分裂成两个节点。

节点分裂的方式是:最中间的关键字向上移到父节点,大于和小于中间关键字的部分分别成为两个新的节点。可以看到,节点分裂的时候父节点得到了一个新的关键字,如果分裂前父节点关键字树已经饱和了(m-1个),会导致父节点也分裂,在最坏的情况下,分裂会一直进行到根节点,根节点一分裂,整个树的高度都会加一。

举个栗子,以下5阶B树:

为了方便,在例子中使用 Pi (i 从 1 到 n)表示指向子节点的指针,并且去掉了指向 NULL 节点的部分。

《查找算法 | B-树详细分析》

加入关键字40:可以很快定位到加入元素的位置是第三个叶子节点,加完之后树变成了这样:

《查找算法 | B-树详细分析》

加入关键字56:定位到新增关键字的位置,第四个叶子节点,加完之后树变成这样:

《查找算法 | B-树详细分析》

加入关键字45:应该添加到第三个叶子节点,加完之后树变成这样:

《查找算法 | B-树详细分析》

还没完,5阶B树每个节点最多4个关键字,这样第三个叶子节点关键字超上限了,需要分裂。中间关键字35上升到父元素,因为35是从P3的节点来的,所以35在父节点中的位置就在小于P3的关键字30和大于P3的关键字50之间。叶子节点中小于35的关键字(31和32)组成新节点,大于35的关键字(40和45)组成新节点,然后树变成这样:

《查找算法 | B-树详细分析》

加入关键字51:应该添加到第5个叶子节点,加完是这样:

《查找算法 | B-树详细分析》

第五个叶子节点关键字数超上限,分裂之后中间元素56上移到父元素,变成这样:

《查找算法 | B-树详细分析》

父节点得到了关键字56之后,关键字数也超上限了,也需要分裂,中间关键字35上移,大于和小于35的关键字分别形成新的节点,P3和P4指针的位置其实还是不变的,最终树变成这样:

《查找算法 | B-树详细分析》

整个树增加了一层。

B-树中删除关键字

从 B-树的节点中删除关键字时,需要考虑删除之后节点的关键字数量是否小于下限。

如果删除之后节点中的关键字小于下限,则需要从子树中获取关键字,或从相邻兄弟节点获取关键字,或和相邻兄弟节点合并。如果从子树获取关键字之后子树依然能保持B树的基本要求,则可以从子树中获取,否则看相邻兄弟节点元素是否富余,富余的话可以从兄弟节点获取元素,相邻兄弟节点也不富余就需要和兄弟节点合并。

从相邻兄弟节点获取关键字时,当然不是把兄弟节点的关键字直接拿过来,而是把当前节点和兄弟节点之间的那个父节点关键字拿过来,然后兄弟节点给父节点补一个关键字。

和兄弟节点合并时,需要把当前节点和兄弟节点之间的那个父节点关键字下移,然后和该节点还有兄弟节点组成新的节点,基于 B-树节点关键字的数量要求,这样合并出来的新节点关键字数不会超上限。这种方式在极端情况下可能会导致根节点下移合并,也就是树的层数会减少1层。

举个栗子,以下五阶B树:

《查找算法 | B-树详细分析》

删除关键字21:最普通的情况,第一个叶子节点删除21之后,剩余关键字数量3,不需要变动,删完了变成这样:

《查找算法 | B-树详细分析》

删除关键字26:删除26之后节点只剩下了30一个关键字,已经不满足B树的要求,可以从子节点中获取关键字。在这个例子中只能从第一个叶子节点中获取,把叶子节点中最接近被删除关键字的元素22移到父节点,删完了是这样:

《查找算法 | B-树详细分析》

删除关键字29:节点删除29之后关键字只剩下了28,已经低于下限,没有子节点可以获取关键字,考虑从相邻兄弟节点获取。第一个叶子节点已经处于下限,只能从第三个叶子节点获取关键字。步骤如下:把第二叶子节点和第三叶子节点之间的父元素关键字30移到第二叶子节点,然后第三叶子节点把距离30最近的关键字31补给父节点,移完之后树是这样的:

《查找算法 | B-树详细分析》

删除关键字12:删除12之后第一叶子节点就只剩下了5,低于下限。在这个例子中该节点没有子节点,也不能从相邻兄弟节点中获取关键字(兄弟节点只有28和30两个关键字,不能再减少),所以只能和相邻兄弟节点合并。合并的步骤如下:把第一叶子节点和第二叶子节点之间的父节点关键字22下移,然后和第一叶子节点和第二叶子节点组成新的节点,合并完成之后的树是这样的:

《查找算法 | B-树详细分析》

此时父节点只有一个关键字31,小于下限,和直接删除该节点的关键字不同,无法再从子节点中获取关键字,在这个例子中也无法从相邻的兄弟节点获取,只能考虑和相邻兄弟节点合并,合并步骤:父节点关键字35下移,和两个子节点组成新的节点,这个节点实际上成为了根节点,整个树的高度减少了1,合并完的结果如下:

《查找算法 | B-树详细分析》

最基本的删除例子就是这样,实际上在层数比较多的情况下,删除操作可能会更复杂一些。

总结

由于 B-树具有分支多层数少的特点,使得它更多的是应用在数据库系统中。除了 B-树,还有专门为文件系统而生的 B+树,在下一节会详细介绍。

    原文作者:B树
    原文地址: https://blog.csdn.net/liupeifeng3514/article/details/83543860
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞