你心里有B树吗?

1,584 阅读10分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第5天,点击查看活动详情

回望B树,展望红黑树

写在前面

文章摘要

  1. B-Tree的概念和性质
  2. B-Tree的查询
  3. B-Tree的添加
  4. B-Tree的删除

阅读准备

  • 建议花10 ~ 15分钟阅读
  • 本文提到的二叉搜索树,推荐阅读:《二叉搜索树的实现与分析》
  • 本文对B树的分析,是为了后面的红黑树做准备的。相对来说较为轻松😲😲
  • 剧透:既然要展望红黑树,那么重点了解 4阶B树

一、B-Tree的概念&性质

(1)概念

B树(英语:B-tree),是一种在计算机科学自平衡的树,能够保持数据有序。这种数据结构能够让查找数据、顺序访问、插入数据及删除的动作,都在O(logn)内完成。B树,概括来说是一个一般化的二叉查找树(binary search tree)一个节点可以拥有2个以上的子节点。与自平衡二叉查找树不同,B树适用于读写相对大的数据块的存储系统,例如磁盘。B树减少定位记录时所经历的中间过程,从而加快存取速度。B树这种数据结构可以用来描述外部存储。这种数据结构常被应用在数据库文件系统的实现上。

  • B树是一种平衡的多路搜索树
  • 多用于文件系统、数据库的实现

image-20221107193146307

  • 如上有一颗B-Tree
  • 其中 1 个节点可以存储超过 2 个元素、并且可以拥有超过2个子节点
  • 是一棵一般化的二叉搜索树,拥有二叉搜索树的一些性质
  • 非常平衡,每一节点的所有子树高度一致,并且较矮

(2)性质

  • 有一棵 m 阶 B树(m ≥ 2),设一个节点存储的元素数量为 x。如果有子节点,设个数为 y 。那么,可以得出以下的结论:
    • 根节点:(1 ≤ x ≤ m - 1)(2 ≤ y ≤ m)
    • 非根节点:(┍ m/2 ┑ - 1 ≤ x ≤ m - 1)(┍ m/2 ┑ ≤ y ≤ m)
      • p.s:┍ x ┑表示对 x 向上取整(如:x = 2.1 => ┍ x ┑ = 3
    • eg:非根节点
      • m = 3,2 ≤ y ≤ 3,1 ≤ x ≤ 2,可称为 (2, 3)树、2-3树
      • m = 4,2 ≤ y ≤ 4,1 ≤ x ≤ 3,可称为 (2, 4)树、2-3-4树
      • m = 5,3 ≤ y ≤ 5,2 ≤ x ≤ 4,可称为(3, 5)树、2-3-4-5树
      • .....

image-20221108143110970

  • 如果m = 2 呢?
  • 如下图,二阶B树,其实就是一棵二叉搜索树

image-20221107194932357

  • 定义中说:B树在逻辑上也是一棵二叉搜索树
  • 如果是阶数为二阶的B树,会退化,完全等同于为一棵二叉搜索树
  • 那我们能否将一棵二叉搜索树,变成一棵 B树呢?
  • 其实是可以的,先来看一个概念:
    • 某节点含有多个元素,并且可以有大于两个的子节点。我们称这样的节点为超级节点
  • 将二叉搜索树的多代合并,可获得一个超级节点
    • 2代合并的超级节点,最多拥有4个子节点,所以至少是4阶B树

image-20221107200023895

  • 依此类推
    • 3代合并的超级节点,最多拥有8个子节点,所以至少是8阶B树
    • n代合并的超级节点,最多拥有 2 ^ n个子节点,所以至少是2的n次方阶B树
  • 反推取对数可得:m阶B树,最多需要log2 m次合并

二、B-Tree的查找

  • 因为B树逻辑上与二叉搜索树等价,在B树中查找元素,也是去比较节点大小。再继续寻找:
    • ①先在节点的内部从小到大开始查找元素
    • 如果找到,查找结束
    • ②如果未找到,再去对应的子节点中搜索元素,重复步骤①

image-20221108143310657

  • 如图,我们想要在这一棵4阶B树中,查找元素 15
  • 从根节点(5、14、22)开始查找,发现 15 应该介于 14 和 22 之间
  • 所以找到子节点(18),发现 15 应该在 18 的左边
  • 所以找到子节点(15),返回结果

三、B-Tree的添加

  • 新添加的元素必定是添加在叶子节点中
  • 如下图,添加了元素:12 和 35

image-20221108144348393

  • 直接搜索到应该放置的叶子节点,将其插入即可
  • 应该很清晰,如果再添加一个元素31,会发生什么呢?

image-20221108144738688

  • 要是不知道B树的性质,是不是感觉也没问题,那要是知道B树的性质呢?
  • 我们会发现,添加后,右下角的节点中存在了4个元素,而4阶B树单个节点最多能容纳 3 个元素
  • 已经溢出了一个元素,我们得将其修复。而这样的现象被称为:上溢(overflow)

上溢的解决

  • 添加完成时,出现了上溢节点,那么该节点的元素个数必然为 m(m为阶数)
  • 且假设该节点最中间元素的位置为 k,则解决方案是:
    • k位置的元素向上与父节点合并
    • [0, k-1] 和 [k+1, m-1]位置的元素分裂成2个子节点
  • 如下图所示

image-20221108145305558

  • 如果m是偶数,那么上溢节点中的元素个数也是偶数,中间 k 可选的位置也会有2个,如上面就可以选择 32、35
  • 我们这里令k = 1,将其位置的元素取出来,将它上移到父节点中,变成:(25、30、32)
  • 再将它[0, 0]和 [2, 3]位置的元素分裂为两个子节点(31)和(35、38)
  • 经过上面的操作,已经没有上溢节点了
  • 那我们再添加 40 和 50 ,看看会有什么问题

image-20221108150219904

  • 也出现了上溢节点,但再看到上溢节点,应该不会慌张了吧!那我们再一起来解决上溢~

image-20221108150337302

  • 按照刚刚的思路解决完上溢,一开始出现的上溢的节点确实没有问题了
  • 可是我们发现,向上合并时,导致父节点也出现了上溢现象
  • 怎么办呢?继续解决呗~

image-20221108150915039

  • 这一次添加元素时,我们发现一个现象:解决完上溢后,可能会导致父节点也上溢
  • 最坏的情况就是像上图所示:一直上溢到了根节点
  • 根节点的上溢被解决后,发现树长高了。是的,这是唯一会使 B 树长高的情况
  • 但是好在,出现这么多次上溢节点,解决上溢的思路都是一致的~😁😁

四、B-Tree的删除

  • 之前二叉搜索树的删除,思路是根据节点的度不同分类讨论其删除的方式
  • 那B树呢?其实也差不多,我们先来看看删除的元素在叶子节点中
  • 如下图,我们想要删除 50

image-20221108153734096

  • 可以看到,50在叶子节点(40、50)中,直接删除即可
  • 那我们看看如果删除的是非叶子节点中的元素 14 呢?

image-20221108154409243

  • 可以发现,其实和二叉搜索树中,删除度为 2 的节点差不多
  • 思路是:
    • 先找到待删除元素所在节点的前驱或后继节点
    • 将其节点中最大或最小的元素赋值给待删除的元素
    • 最后删除前驱或后继中用来赋值的元素即可
  • 而B树的前驱或后继节点,必然是叶子节点。 而删除叶子节点中的元素,就可以直接删除了
  • 那再来看几种情况。为了方便描述情况,我们假设有一棵5阶B树

image-20221108171622994

  • 删除元素30,会发生什么呢?

image-20221108171751368

  • 发现30在叶子节点中,按刚刚的逻辑,直接将其元素删除。的确如此,这一步操作没问题
  • 可是这是 5 阶 子树哎,必须满足:┍ m/2 ┑ - 1 ≤ 非根节点中元素数量 ≤ m - 1,也就是元素数量必须属于 [2, 4]
  • 而在上图中,我们将节点(30,32)的 30 删除了。此节点变成了(32),元素数量为1,已经 低于最少元素数量的个数了。这种情况被称为:下溢(underflow)
  • 那么如何解决下溢呢?

下溢的解决

  • 删除完成时,出现了下溢节点,那么它必然满足:元素个数 == ┍ m/2 ┑ - 2(m为阶数)
  • 如果当下溢节点的临近兄弟节点中的元素个数,最少为┍ m/2 ┑时,可以向其借一个元素。(也就是兄弟节点给你一个元素,它也不会产生下溢。那么就可以向它借一个元素
  • 如解决上图的下溢情况

image-20221108172833026

  • 向兄弟节点借元素时。因为有大小关系,不可以直接拿过来,只能通过巧妙的交换
    • 将父节点中(28,34),元素28移动到下溢节点,使其变成(28,32)
    • 将可以借元素的临近兄弟节点(22,24,27)中的元素27,移动到父节点中,使其变成(27,34)
    • 即可解决下溢。听我这样一描述,是不是发现,其实这就是在旋转啊~
    • 我上面的操作其实就是在右旋,因为临近可借用元素的兄弟节点在左边,如果在右边,那么左旋即可修复下溢节点
  • 我们刚刚一直在讨论,出现下溢时,可以向兄弟节点借用一个元素的情况,那如果没得借呢?

image-20221108173211186

  • 如上图所示,被删除元素的节点都没有临近的兄弟节点
  • 别谈不够借了,是根本借不到,那又该如何解决呢?

image-20221108173810165

  • 如图所示,删除节点(1,2)中的元素2,出现了下溢现象,可它根本就没有临近的兄弟节点
  • 那么解决的思路是:在父节点中取出一个元素,向下与兄弟节点合并(如果有兄弟节点)
  • 在这里,合并完成后确实修复了之前的下溢节点
  • 与添加出现上溢相似,又出现了新的下溢节点,最坏的情况如上图所示:会一直下溢到根节点
  • 刚刚说如果有兄弟节点,需要与兄弟节点一起合并,也就是下面这种情况

image-20221108174806510

  • 如上图所示,有临近的兄弟节点,但是它没有多的元素借给下溢的兄弟节点
  • 这时,需要从父节点中取出一个元素,将原先的下溢节点与它临近的兄弟节点合并(如有多个不能外借的临近兄弟节点,只能合并一个)
  • 即可解决解决。若这样的操作使父节点也下溢了,重复上面的思路,依次解决即可
  • 如果根节点的下一层出现了下溢节点。当下溢节点全部被修复,重新平衡时,该树整体的高度会减1
  • 这也是唯一能使B树变矮的操作了

写在后面

本篇收获

  1. 初步了解了B树
  2. B树添加元素时,出现上溢现象的解决方案
  3. B树删除元素时,出现下溢现象的解决方案

读后思考

  • 心里有了B树,真的对红黑树有用吗?有什么用呢?又怎么用呢?
  • 随机给定一组元素,来构建4阶B树,该如何操作呢?
  • 再将他们依次删除呢,又该如何操作呢?