开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第5天,点击查看活动详情
回望B树,展望红黑树
写在前面
文章摘要
- B-Tree的概念和性质
- B-Tree的查询
- B-Tree的添加
- B-Tree的删除
阅读准备
- 建议花10 ~ 15分钟阅读
- 本文提到的二叉搜索树,推荐阅读:《二叉搜索树的实现与分析》
- 本文对B树的分析,是为了后面的红黑树做准备的。相对来说较为轻松😲😲
- 剧透:既然要展望红黑树,那么重点了解
4阶B树
一、B-Tree的概念&性质
(1)概念
B树(英语:B-tree),是一种在计算机科学自平衡的树,能够保持数据有序。这种数据结构能够让查找数据、顺序访问、插入数据及删除的动作,都在
O(logn)内完成。B树,概括来说是一个一般化的二叉查找树(binary search tree)一个节点可以拥有2个以上的子节点。与自平衡二叉查找树不同,B树适用于读写相对大的数据块的存储系统,例如磁盘。B树减少定位记录时所经历的中间过程,从而加快存取速度。B树这种数据结构可以用来描述外部存储。这种数据结构常被应用在数据库和文件系统的实现上。
- B树是一种平衡的多路搜索树
- 多用于文件系统、数据库的实现
- 如上有一颗
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)
- p.s:
- 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树 - .....
- m = 3,2 ≤ y ≤ 3,1 ≤ x ≤ 2,可称为
- 根节点:
- 如果m = 2 呢?
- 如下图,二阶B树,其实就是一棵二叉搜索树
- 定义中说:B树在逻辑上也是一棵二叉搜索树
- 如果是阶数为二阶的B树,会退化,完全等同于为一棵二叉搜索树
- 那我们能否将一棵二叉搜索树,变成一棵 B树呢?
- 其实是可以的,先来看一个概念:
- 某节点含有多个元素,并且可以有大于两个的子节点。我们称这样的节点为超级节点
- 将二叉搜索树的多代合并,可获得一个超级节点
- 2代合并的超级节点,最多拥有4个子节点,所以至少是4阶B树
- 依此类推
- 3代合并的超级节点,最多拥有8个子节点,所以至少是8阶B树
- n代合并的超级节点,最多拥有
2 ^ n个子节点,所以至少是2的n次方阶B树
- 反推取对数可得:m阶B树,最多需要
log2 m次合并
二、B-Tree的查找
- 因为B树逻辑上与二叉搜索树等价,在B树中查找元素,也是去比较节点大小。再继续寻找:
- ①先在节点的内部从小到大开始查找元素
- 如果找到,查找结束
- ②如果未找到,再去对应的子节点中搜索元素,重复步骤①
- 如图,我们想要在这一棵4阶B树中,查找元素 15
- 从根节点(5、14、22)开始查找,发现 15 应该介于 14 和 22 之间
- 所以找到子节点(18),发现 15 应该在 18 的左边
- 所以找到子节点(15),返回结果
三、B-Tree的添加
- 新添加的元素必定是添加在叶子节点中
- 如下图,添加了元素:12 和 35
- 直接搜索到应该放置的叶子节点,将其插入即可
- 应该很清晰,如果再添加一个元素
31,会发生什么呢?
- 要是不知道B树的性质,是不是感觉也没问题,那要是知道B树的性质呢?
- 我们会发现,添加后,右下角的节点中存在了4个元素,而4阶B树单个节点最多能容纳 3 个元素
- 已经溢出了一个元素,我们得将其修复。而这样的现象被称为:上溢(overflow)
上溢的解决
- 添加完成时,出现了上溢节点,那么该节点的元素个数必然为
m(m为阶数) - 且假设该节点最中间元素的位置为
k,则解决方案是:- 将
k位置的元素向上与父节点合并 - 将
[0, k-1] 和 [k+1, m-1]位置的元素分裂成2个子节点
- 将
- 如下图所示
- 如果m是偶数,那么上溢节点中的元素个数也是偶数,中间 k 可选的位置也会有2个,如上面就可以选择 32、35
- 我们这里令
k = 1,将其位置的元素取出来,将它上移到父节点中,变成:(25、30、32) - 再将它
[0, 0]和 [2, 3]位置的元素分裂为两个子节点(31)和(35、38) - 经过上面的操作,已经没有上溢节点了
- 那我们再添加 40 和 50 ,看看会有什么问题
- 也出现了上溢节点,但再看到上溢节点,应该不会慌张了吧!那我们再一起来解决上溢~
- 按照刚刚的思路解决完上溢,一开始出现的上溢的节点确实没有问题了
- 可是我们发现,向上合并时,导致父节点也出现了上溢现象
- 怎么办呢?继续解决呗~
- 这一次添加元素时,我们发现一个现象:解决完上溢后,可能会导致父节点也上溢
- 最坏的情况就是像上图所示:一直上溢到了根节点
- 根节点的上溢被解决后,发现树长高了。是的,这是唯一会使 B 树长高的情况
- 但是好在,出现这么多次上溢节点,解决上溢的思路都是一致的~😁😁
四、B-Tree的删除
- 之前二叉搜索树的删除,思路是根据节点的度不同分类讨论其删除的方式
- 那B树呢?其实也差不多,我们先来看看删除的元素在叶子节点中
- 如下图,我们想要删除 50
- 可以看到,50在叶子节点(40、50)中,直接删除即可
- 那我们看看如果删除的是非叶子节点中的元素 14 呢?
- 可以发现,其实和二叉搜索树中,删除度为 2 的节点差不多
- 思路是:
- 先找到待删除元素所在节点的前驱或后继节点
- 将其节点中最大或最小的元素赋值给待删除的元素
- 最后删除前驱或后继中用来赋值的元素即可
- 而B树的前驱或后继节点,必然是叶子节点。 而删除叶子节点中的元素,就可以直接删除了
- 那再来看几种情况。为了方便描述情况,我们假设有一棵5阶B树
- 删除元素30,会发生什么呢?
- 发现30在叶子节点中,按刚刚的逻辑,直接将其元素删除。的确如此,这一步操作没问题
- 可是这是 5 阶 子树哎,必须满足:
┍ m/2 ┑ - 1 ≤ 非根节点中元素数量 ≤ m - 1,也就是元素数量必须属于[2, 4]中 - 而在上图中,我们将节点(30,32)的 30 删除了。此节点变成了(32),元素数量为1,已经 低于最少元素数量的个数了。这种情况被称为:下溢(underflow)
- 那么如何解决下溢呢?
下溢的解决
- 删除完成时,出现了下溢节点,那么它必然满足:
元素个数 == ┍ m/2 ┑ - 2(m为阶数) - 如果当下溢节点的临近兄弟节点中的元素个数,最少为
┍ m/2 ┑时,可以向其借一个元素。(也就是兄弟节点给你一个元素,它也不会产生下溢。那么就可以向它借一个元素) - 如解决上图的下溢情况
- 向兄弟节点借元素时。因为有大小关系,不可以直接拿过来,只能通过巧妙的交换
- 将父节点中(28,34),元素28移动到下溢节点,使其变成(28,32)
- 将可以借元素的临近兄弟节点(22,24,27)中的元素27,移动到父节点中,使其变成(27,34)
- 即可解决下溢。听我这样一描述,是不是发现,其实这就是在旋转啊~
- 我上面的操作其实就是在右旋,因为临近可借用元素的兄弟节点在左边,如果在右边,那么左旋即可修复下溢节点
- 我们刚刚一直在讨论,出现下溢时,可以向兄弟节点借用一个元素的情况,那如果没得借呢?
- 如上图所示,被删除元素的节点都没有临近的兄弟节点
- 别谈不够借了,是根本借不到,那又该如何解决呢?
- 如图所示,删除节点(1,2)中的元素2,出现了下溢现象,可它根本就没有临近的兄弟节点
- 那么解决的思路是:在父节点中取出一个元素,向下与兄弟节点合并(如果有兄弟节点)
- 在这里,合并完成后确实修复了之前的下溢节点
- 与添加出现上溢相似,又出现了新的下溢节点,最坏的情况如上图所示:会一直下溢到根节点
- 刚刚说如果有兄弟节点,需要与兄弟节点一起合并,也就是下面这种情况
- 如上图所示,有临近的兄弟节点,但是它没有多的元素借给下溢的兄弟节点
- 这时,需要从父节点中取出一个元素,将原先的下溢节点与它临近的兄弟节点合并(如有多个不能外借的临近兄弟节点,只能合并一个)
- 即可解决解决。若这样的操作使父节点也下溢了,重复上面的思路,依次解决即可
- 如果根节点的下一层出现了下溢节点。当下溢节点全部被修复,重新平衡时,该树整体的高度会减1
- 这也是唯一能使B树变矮的操作了
写在后面
本篇收获
- 初步了解了B树
- B树添加元素时,出现上溢现象的解决方案
- B树删除元素时,出现下溢现象的解决方案
读后思考
- 心里有了B树,真的对红黑树有用吗?有什么用呢?又怎么用呢?
- 随机给定一组元素,来构建4阶B树,该如何操作呢?
- 再将他们依次删除呢,又该如何操作呢?