数据结构之「B树」

2,601 阅读5分钟

B树

B树(B-tree)是一种自平衡的树,能够保持数据有序。是一种专用的 M 阶树,最多有 M 个节点的自平衡树,与自平衡二叉查找树不同,B树 为系统大块数据的读写操作做了优化。B树 减少定位记录时所经历的中间过程,从而加快存取速度。这种数据结构常被应用在 数据库文件系统 的实现上。

这种数据结构能够让查找数据、顺序访问、插入数据及删除的动作,都在对数时间内完成。

一棵 M阶 的 B树 要满足以下条件
1.每一个节点最多有 M 个子节点。
2.除了根节点和叶子节点外,其他每个节点最少有 M/2 个子节点。
3.根节点不是叶子节点,它至少有 2 个子节点。
4.有 k 个子节点的非叶子节点拥有 k−1 个键。
5.所有的叶子节点必须在同一层。

B树

B树的实现

1.搜索
B树的搜索和二叉搜索树类似。从根节点开始,从上到下递归的遍历树。在每一层上,搜索的范围被减小到包含了搜索值的子树中。子树值的范围被它的父节点的键确定。

搜索下图中的数字 49
a.用 49 和根节点元素比较,由于 49 < 78,所以继续遍历左子树。
b.由于 40<49<56,所以遍历节点 40 的右子树。
c.49>45,往右移。
d.找到 49,返回。

2.插入

从根节点开始,搜索这棵树找到新元素应该被添加到的叶子节点。如果节点拥有的元素数量小于最大值,那么有空间容纳新的元素。将新元素插入到这一节点,且保持节点中元素有序。否则的话,需要将它平均地分裂成两个节点,从叶子节点的元素和新的元素中选择出中位数,小于这一中位数的元素放入左边节点,大于这一中位数的元素放入右边节点,中位数作为分隔值。

分隔值被插入到父节点中,这可能会造成父节点分裂,分裂父节点时可能又会使它的父节点分裂,以此类推。如果没有父节点(这一节点是根节点),就创建一个新的根节点(增加了树的高度)。

插入元素 8 到下图的 5阶 B树中

由于 8 大于 5,插入到 5 的后面。

由于一棵 B树最多包含 k-1 个键,然而这个节点包含了 5 个键,所以需要按中位拆分,8 变成父节点。

3.删除

假如删除的是叶子节点,将它删除后,重新进行平衡。

假如删除的是中间节点,每一个元素都作为分隔两颗子树的分隔值,因此我们需要重新划分。选择一个新的分隔符(左子树中最大的元素或右子树中最小的元素),将它从叶子节点中移除,替换掉被删除的元素作为新的分隔值。如果删除的这个叶子节点拥有的元素数量小于最低要求,那么从这一叶子节点开始重新进行平衡。

删除下面 5阶B树 为 53 的节点

找到 53 在 49 的右孩子里,然后删掉它。

由于 B树 的中间节点个数不得低于 m/2,现在只剩下一个节点,已经违反了规则,就需要按下面的重新平衡算法去满足它的特性,最后这棵树变成下面这样的。

重新平衡算法

如果缺少元素节点的右兄弟节点存在且拥有多余的元素,将父节点的分隔值复制到缺少元素节点的最后,将父节点的分隔值替换为右兄弟的第一个元素。

如果缺少元素节点的左兄弟节点存在且拥有多余的元素,将父节点的分隔值复制到缺少元素节点的最后,将父节点的分隔值替换为左兄弟的第一个元素。

如果它的两个直接兄弟节点都只有最小数量的元素,那么将它与一个直接兄弟节点以及父节点中它们的分隔值合并,将分隔值复制到左边的节点,将右边节点中所有的元素移动到左边节点,将父节点中的分隔值和空的右子树移除,如果父节点是根节点并且没有元素了,那么释放它并且让合并之后的节点成为新的根节点,否则,如果父节点的元素数量小于最小值,重新平衡父节点。

总结

B树 实际上是二叉搜索树的升级版,由于二叉树只有 2 个子节点,当数据量大的时候树会变得非常高,也会对性能有一定影响。硬盘的读取是按块来读取的,一般是 4k。因此我们可以根据这一特性,把 2 个节点变为 M 个节点,每次读取减少定位记录时所经历的中间过程,从而加快存取速度。

B树 在插入和删除时,都有可能导致破坏 B树 的规则,因此当破坏规则时,需要对树进行重新平衡,已到达查找数据、顺序访问、插入数据及删除时的时间复杂度在 O(log n)。

B树 的变体 B+树,键值和记录存储在叶子节点,一个叶子节点可以包含一个指针,指向另一个叶子节点以加速顺序存取。

B树 的变体 B*树,分支出更多的内部邻居节点以保持内部节点更密集地填充。此变体要求非根节点至少 2/3 填充,而不是 1/2。为了维持这样的结构,当一个节点填满之后将不会再立即分割节点,而是将它的键值与下一个节点共享。当两个节点都填满之后,分割成 3 个节点。

PS:
清山绿水始于尘,博学多识贵于勤。
我有酒,你有故事吗?
公众号:「清尘闲聊」。
欢迎一起谈天说地,聊代码。