彻底理解B树

前言

最近我们的项目使用到MongoDB,因为之前的数据存储都是选择MySql或者PostgressSQL,为什么这个项目要选择MongoDB呢?进一步了解到原来MongoDB的默认存储引擎是WridedTiger,并使用B树作为索引底层的数据结构。本着好奇最后打算对B树进行深入了解。

一、B树是什么?

查阅了相关资料了解到,B树英文名叫B-Tree(Balance-Tree),是一种平衡多路搜索树,多用在文件系统、数据库的实现。假设一颗m阶B树它满足以下性质:
1.根结点的子结点至少有两个,除非当根结点就是叶子结点时,此时B树只有一个结点。
2.每个结点至多有m个子结点
3.除根结点和叶子结点外,其它每个结点至少有 ⌈ m 2 ⌉ \lceil \frac{m}{2}\rceil 2m个子结点
4.所有叶子结点都在同一层
5.每个非根结点包含关键字个数k满足: ⌈ m 2 ⌉ − 1 ≤ k ≤ m − 1 \lceil \frac{m}{2}\rceil-1 \leq k \leq m-1 2m1km1
6.根结点包含关键字个数k满足: 1 ≤ k ≤ m − 1 1 \leq k \leq m-1 1km1
如下图显示的是5阶B树
彻底理解B树_第1张图片

B树搜索

在B树中查找给定的关键字,首先遍历根结点包含的关键字,可以是顺序查找或者二分法查找。如果找到指定关键字则搜索结束,找不到则根据关键字是在某个ki和ki-1的区间,然后取ki所在的结点继续查找,找到则返回搜索值,还是找不到则继续去对应结点搜索。
例如我们要在下面5阶B树搜索17关键字
1.首先会取根结点9关键字进行比较
2.因9<17没有命中,然后找到它右结点包含的关键字12,15,18,从左到右进行比较没有命中,继续遍历它的子结点。
3.最后因15<17<18,找到了15~18的子结点包含的关键字16,17进行遍历比较,最终成功命中17,返回值。
彻底理解B树_第2张图片

B树插入

在B树中插入指定的关键字,插入的关键字必然是在叶子结点,通过搜索找到插入关键字的结点,然后分两种情况:
1.如果插入结点的关键字数量少于m-1,则直接插入。
2.如果插入结点的关键字数量等于m-1,插入后会引起分裂。假设结点关键字的中间位置为k,则会将K位置的关键字向上父节点合并。(0,k-1)和(k+1,m)位置的关键字会分裂成两个子结点。一次分裂后有可能会导致父结点上溢,因此也会导致父节点分裂,最极端的情况下有可能会一直分裂到父结点。

B树删除

在B树中删除指定的关键字,通过搜索找到要删除结点的关键字进行删除,相对比较复杂有几种情况:
1.假如是在叶子结点,它的关键字数量大于 ⌈ m 2 ⌉ − 1 \lceil \frac{m}{2}\rceil-1 2m1,那么直接删除即可。
2.假如是在叶子结点,它的关键字数量恰好等于 ⌈ m 2 ⌉ − 1 \lceil \frac{m}{2}\rceil-1 2m1,那么删除后剩余的关键字必然等于 ⌈ m 2 ⌉ − 2 \lceil \frac{m}{2}\rceil-2 2m2,如果左临近的兄弟结点至少有 ⌈ m 2 ⌉ \lceil \frac{m}{2}\rceil 2m关键字,可以将它最大的关键字,替换父结点的关键字,被替换父结点的关键字插入到删除结点的最少位置。这种操作也叫右旋转。
3.假如是在叶子结点,它的关键字数量恰好等于 ⌈ m 2 ⌉ − 1 \lceil \frac{m}{2}\rceil-1 2m1,那么删除后剩余的关键字必然等于 ⌈ m 2 ⌉ − 2 \lceil \frac{m}{2}\rceil-2 2m2,如果左临近的兄弟结点关键字也恰好等于 ⌈ m 2 ⌉ − 1 \lceil \frac{m}{2}\rceil-1 2m1,可以将它父节点关键字挪下来与左右结点关键字进行合并,这种情况会导致父结点下溢,最极端的情况下溢现象可能会一直往上传播,直到根结点。
4.假如是在非叶子结点,需要找到前驱或者后继关键字,然后覆盖要删除的关键字,最后在删除前驱或者后继关键字,注意删除的前驱或者后继关键字必然在叶子节点。

B树高度

从上面的5阶B树视图展示我们可以了解到,当n>=1,则对于任意一棵包含n个关键字、高度为h、阶数为m的B树。B树每层结点数最大等于m时,B树的高度将会是最低。相反B树的每层结点数等于 ⌈ m 2 ⌉ \lceil \frac{m}{2}\rceil 2m时,B树的高度将会是最大。因此根据这两个特性,可以推导出B树的最大高度、最小高度。

最小高度

B树每层结点数最大等于m时,B树的高度将会是最低

结点个数 层数
1 0
m 1
m 2 m^2 m2 2
m 3 m^3 m3 3
m h − 1 m^{h-1} mh1 h-1

因此可以得出,对于高度为h,m阶的B树最大总结点数为: 1 + m + m 2 + m 3 + . . . m h − 1 1+m+m^2+m^3+...m^{h-1} 1+m+m2+m3+...mh1
最大的总关键字数等于最大总结点数*每个结点最大关键数: n ≤ ( 1 + m + m 2 + m 3 + . . . m h − 1 ) ∗ ( m − 1 )    ⟹    n ≤ m h − 1    ⟹    l o g m n + 1 ≤ h n \leq(1+m+m^2+m^3+...m^{h-1})*(m-1) \implies n \leq m^{h-1} \implies log_mn+1 \leq h n1+m+m2+m3+...mh1)(m1)nmh1logmn+1h
最后得出结论:
当n>=1,包含n个关键字、高度为h、阶数为m的B树,最小高度为: l o g m n + 1 log_mn+1 logmn+1

最大高度

B树的每层结点数等于 ⌈ m 2 ⌉ \lceil \frac{m}{2}\rceil 2m时,也就是每个结点的关键字最少时,B树的高度将会是最大。

结点个数 层数
1 0
2 1
2 ⌈ m 2 ⌉ 2\lceil \frac{m}{2}\rceil 22m 2
2 ⌈ m 2 ⌉ 2 2\lceil \frac{m}{2}\rceil ^2 22m2 3
. . .. ..
2 ⌈ m 2 ⌉ h − 2 2\lceil \frac{m}{2}\rceil ^{h-2} 22mh2 h-1
2 ⌈ m 2 ⌉ h − 1 2\lceil \frac{m}{2}\rceil ^{h-1} 22mh1 h

注意h层对应的结点是外部结点,也就是叶结点的子结点,实际上不存放数据的,外部结点个数刚好等于n-1。
因此有得出结论: 2 ⌈ m 2 ⌉ h − 1 ≤ n − 1    ⟹    h ≤ l o g ⌈ m 2 ⌉ n + 1 2 2\lceil \frac{m}{2}\rceil ^{h-1} \leq n-1 \implies h \leq log_{\lceil {m\over2} \rceil} \frac{n+1}{2} 22mh1n1hlog2m2n+1

外部节点等于n-1

刚开始我在思考计算B树的最大高度时,怎么也想不通为什么这么巧,对于包含n个关键字、高度为h、阶数为m的B树,外部节点等于n-1呢
推导开始:
首先根据B树的特性可以知道一个结点的子结点刚好等于结点的关键数+1,因此我们可以假设:

结点个数 层数
1 0
第0层结点的关键字数量+1 1
第1层所有结点的关键字数量+第1层结点数 2
第2层所有结点的关键字数量+第2层结点数 3
第h-2层所有结点的关键字数量+第h-2层结点数 h-1(叶结点)
第h-1层所有结点的关键字数量+第h-1层结点数 h(外部结点)

最后可知,外部结点数刚好等于B树的关键字+1

B树应用

对B树有了深刻认识,进一步对B树应用进行探讨。我们之前学过平衡二叉树像AVL树,红黑树查询的时间复杂度是O(logN),查询速度非常快了。但是当数据量特别大时,内存就会不够用。我们也可以把数据以AVL树或红黑树数据结构形式存到磁盘,由于他们是二叉树,数据特别大时树深度高,读取磁盘IO次数就会越多,因此像AVL数,红黑树从设计上无法迎合磁盘。
既然树的深度和读取磁盘IO次数有关,就要想办法降低树的深度,减少读取磁盘IO的次数,将“瘦高”的树变得“矮胖”。B树是一种平衡多路搜索树,每个结点至少有2个或者几千个孩子结点,整体上大大降低了树的高度,因此B树这种数据结构最适合应用在磁盘上。

你可能感兴趣的:(数据结构与算法)