B树

1.须知

B-树是一种平衡的多路查找树,注意: B树就是B-树,"-"是个连字符号,不是减号 。
我们假设我们的数据量达到了亿级别,主存当中根本存储不下,我们只能以块的形式从磁盘读取数据,与主存的访问时间相比,磁盘的 I/O 操作相当耗时,而提出 B-树的主要目的就是减少磁盘的 I/O 操作。
大多数平衡树的操作(查找、插入、删除,最大值、最小值等等)需要O(h)次磁盘访问操作,其中h是树的高度。
但是对于B-树而言,树的高度将不再是logn,而是一个我们可控的高度h。
一般而言,B树的结点所包含的键的数目和磁盘块大小一样,从数个到数千个不等。由于B树的高度h可控(一般远远小于logn),所以B树的磁盘访问时间将极大的降低。

2.B树的特性

  1. 所有的叶子结点都出现在同一层上,并且不带信息
  2. 每个结点包含的关键字个数有上界和下界。用一个被称为B树的最小度数的固定整数t>=2来表示这些界,其中t取决于磁盘块的大小。
    除了根节点以外的每个结点必须至少有t-1个关键字。
    因此,除了根节点以外的每个内部节点至少有t个孩子。如果树非空,根节点至少有一个关键字。
    每个结点至多包含2t-1个关键字。
  3. 一个包含x个关键字的结点有x+1个孩子
  4. 一个结点中的所有关键字升序排列,两个关键字k1和k2之间的孩子结点的所有关键字 key 在(k1,k2)的范围之内。
  5. 与二叉排序树不同,B-树的搜索是从根结点开始,根据结点的孩子树做多路分支选择,而二叉排序树做的是二路分支选择,每一次判断都会进行一次磁盘I/O操作。
  6. 与其他平衡二叉树类似,B树查找、插入和删除操作的时间复杂度为O(log n)量级。

    上图解释:
    上图就是一颗典型的B-树,其中最小度数2,根结点至少包含一个关键字p,根结点以外的每个结点至少有t-1=1 个,每个结点最多包含2t-1=3个关键字;包含1关键字p的根结点有1+1=2个孩子结点,包含3个关键字的结点(C、G、L) 包含有4个孩子。同一个结点中的所有关键字升序排列,比如结点(D、E、F)的内部结点就是升序排列,且均位于其父结点中的关键字C和G之间。所有的叶结点均为空。

3.B树的查找


我们以查找关键字 F 为例进行说明。
第一步:访问根结点 P ,发现关键字 F 小于 P ,则查找结点 P 的左孩子。

第二步:访问结点 P 的左子结点 [C、G、L] ,对于一个结点中包含多个关键字时,顺序进行访问,首先与关键字 C 进行比较,发现比 C 大;然后与关键字 G 进行比较,发现比 G 小,则说明待查找关键字 F 位于关键字 C 和关键字 G 之间的子代中。

第三步:访问关键字 C 和关键字 G 之间的子代,该子代结点包含三个关键字 [D、E、F] ,进行顺序遍历,比较关键字 D 和 F ,F 比 D 大

顺序访问关键字 E ,F 比 E 大:

顺序访问关键字 F ,发现与待查找关键字相同,查找成功。则返回结点 [D、E、F] 的指针。

4.B树的中序遍历

B-树的中序遍历与二叉树的中序遍历也很相似,我们从最左边的孩子结点开始,递归地打印最左边的孩子结点,然后对剩余的孩子和关键字重复相同的过程。最后,递归打印最右边的孩子.

对于这个图的中序遍历结果为:

一定要注意,本应该是26个字母,但是这里缺少了字母I,之后我们看插入操作时可以将其插入。

5.B树插入操作

一个新插入的关键字k总是被插入到叶子结点。与二叉排序树的插入操作类似,我们从根结点开始,向下遍历直到叶子结点,到达叶子结点,将关键字 k 插入到相应的叶子结点。与 BST 不同的是,我们通过最小度定义了一个结点可以包含关键字的个数的一个取值范围,所以在插入一个关键字时,就需要确认插入关键字之后结点是否超出结点本身最大可容纳的关键字个数。
如何判断在插入一个关键字 k 之前,一个结点是否有可供当前结点插入的空间呢?
我们可以使用一个称为splitChild()的操作实现,即拆分一个结点的孩子。下图中,x的孩子结点y被拆分成了两个结点 y 和 z 。拆分操作将一个关键I上移,并以上移的关键 I对结点y进行拆分,拆分成包含关键字[G、H]的结点y和包含关键字[J、K]的结点z.这一过程又称之为B树的生长,区别于BST的向下生长。

综上,B-树在插入一个新的关键字 k 时,我们从根结点一直访问到叶子结点,在遍历一个结点之前,首先检查这个结点是否已经满了,即包含了 2t - 1 个关键字,如果结点已满,则将其拆分并创建新的空间。插入操作的伪代码描述如下:

1. 初始化 x 作为根结点
2. 当 x 不是叶子结点,执行如下操作:
找到 x 的下一个要被访问的孩子结点 y
如果 y 没有满,则将结点 y 作为新的 x
如果 y 已经满了,拆分 y ,结点 x 的指针指向结点 y 的两部分。 如果 k 比 y 中间的关键字小, 则将 y 的第一部分作为新的 x ,否则将 y 的第二部分作为新的 x ,当将 y 拆分后,将 y 中的一个关键字移动到它的父结点 x 当中。
当 x 是叶子结点时,第二步结束; 由于我们已经提前查分了所有结点,x 必定至少有一个额外的关键字空间,进行简单的插入即可。

插入分析

事实上 B树的插入操作是一种主动插入算法,因为在插入新的关键字之前,我们会将所有已满的结点进行拆分,提前拆分的好处就是,我们不必进行回溯,遍历结点两次。如果我们不事先拆分一个已满的结点,而仅仅在插入新的关键字时才拆分它,那么最终可能需要再次从根结点出发遍历所有结点,比如在我们到达叶子结点时,将叶结点进行拆分,并将其中的一个关键字上移导致父结点分裂(因为上移导致父结点超出可存储的关键字的个数),父结点的分裂后,新的关键字继续上移,将可能导致新的父结点分裂,从而出现大量的回溯操作。但是 B-树这种主动插入算法中,就不会发生级联效应。当然,这种主动插入的缺点也很明显,我们可能进行很多不必要的拆分操作。

操作案例


我们以在上图中插入关键字 I 为例进行说明。其中最小度 t = 2 ,一个结点最多可存储 2t - 1 = 3 个结点。
第一步:访问根结点,发现插入关键字 I 小于 P , 但根结点未满,不分裂,直接访问其第一个孩子结点。

第二步:访问结点 P 的第一个孩子结点 [C、G、L] ,发现第一个孩子结点已满,将第一个孩子结点分裂为两个:

第三步:将结点 I 插入到结点 L 的第一个左孩子当中,发现 L 的第一个左孩子 [H、J、K] 已满,则将其分裂为两个。

第四步:将结点 I 插入到结点 J 的第一个孩子当中,发现 L 的第一个孩子结点 H 未满且为叶子结点,则将 I 直接插入。

6.B树删除操作

B-树的删除操作相比于插入操作更为复杂,如果仅仅只是删除叶子结点中的关键字,也非常简单,但是如果删除的是内部节点的,就不得不对结点的孩子进行重新排列。

与 B-树的插入操作类似,我们必须确保删除操作不违背 B-树的特性。正如插入操作中每一个结点所包含的关键字的个数不能超过 2t -1 一样,删除操作要保证每一个结点包含的关键字的个数不少于 t -1 个(除根结点允许包含比 t -1 少的关键字的个数。)

案例

在 B-树种删除关键字时,首先前提是找到该关键字所在结点,在做删除操作的时候分为两种情况,一种情况是删除结点为 B-树的非终端结点(不处在最后一层);另一种情况是删除结点为 B-树最后一层的非终端结点。

例如图 3 来说,关键字 24、45、53、90属于不处在最后一层的非终端结点,关键字 3、12、37等同属于最后一层的非终端结点。

如果该结点为非终端结点且不处在最后一层,假设用 Ki 表示,则只需要找到指针 Ai 所指子树中最小的一个关键字代替 Ki,同时将该最小的关键字删除即可。

如果该结点为最后一层的非终端结点,有下列 3 种可能:
被删关键字所在结点中的关键字数目不小于⌈m/2⌉,则只需从该结点删除该关键字 Ki 以及相应的指针 Ai

例如,在图 3 中,删除关键字 12 ,只需要删除该关键字 12以及右侧指向 NULL 指针即可。

被删关键字所在结点中的关键字数目等于⌈m/2⌉-1,而与该结点相邻的右兄弟结点(或者左兄弟)结点中的关键字数目大于⌈m/2⌉-1,只需将该兄弟结点中的最小(或者最大)的关键字上移到双亲结点中,然后将双亲结点中小于(或者大于)且紧靠该上移关键字的关键字移动到被删关键字所在的结点中。

例如在图 3 中删除关键字 50,其右兄弟结点 g 中的关键字大于2,所以需要将结点 g 中最小的关键字 61 上移到其双亲结点 e 中(由此 e 中结点有:53,61,90),然后将小于 61 且紧靠 61 的关键字 53 下移到结点 f 中,最终删除后的 B-树如图 10 所示。

!

被删除关键字所在的结点如果和其相邻的兄弟结点中的关键字数目都正好等于⌈m/2⌉-1,假设其有右兄弟结点,且其右兄弟结点是由双亲结点中的指针 Ai 所指,则需要在删除该关键字的同时,将剩余的关键字和指针连同双亲结点中的 Ki 一起合并到右兄弟结点中。
例如,在图 10 中 B-树中删除关键字 53,由于其有右兄弟,且右兄弟结点中只有 1 个关键字。在删除关键字 53 后,结点 f 中只剩指向叶子结点的空指针,连同双亲结点中的 61(因为 61 右侧指针指向的兄弟结点 g)一同合并到结点 g 中,最终删除 53 后的 B-树为:

在合并的同时,由于从双亲结点中删除一个关键字,若导致双亲结点中关键字数目小于⌈m/2⌉-1,则继续按照该规律进行合并。例如在图 11 中 B-树的情况下删除关键字 12 时,结点 c 中只有一个关键字,然后做删除关键字 37 的操作。此时在删除关键字 37 的同时,结点 d 中的剩余信息(空指针)同双亲结点中的关键字 24 一同合并到结点 c 中,效果图为:

由于结点 b 中一个关键字也没有,所以破坏了B-树的结构,继续整合。在删除结点 b 的同时,由于 b 中仅剩指向结点 c 的指针,所以连同其双亲结点中的 45 一同合并到其兄弟结点 e 中,最终的B-树为:

posted @ 2022-03-29 10:35  jsqup  阅读(53)  评论(0编辑  收藏  举报