B+ 树
引入
B+ 树是 B 树 的一个升级,它比 B 树更适合实际应用中操作系统的文件索引和数据库索引。目前现代关系型数据库最广泛的支持索引结构就是 B+ 树。
B+ 树是一种多叉排序树,即每个节点通常有多个孩子。一棵 B+ 树包含根节点、内部节点和叶子节点。根节点可能是一个叶子节点,也可能是一个包含两个或两个以上孩子节点的节点。
B+ 树的特点是能够保持数据稳定有序,其插入与修改拥有较稳定的对数时间复杂度。B+ 树元素自底向上插入,这与二叉树恰好相反。
首先介绍一棵
- 有
棵子树的节点中含有 个关键字(即每个关键字对应一棵子树)。 - 所有叶子节点中包含了全部关键字的信息,及指向含这些关键字记录的指针,且叶子节点本身依关键字的大小自小而大顺序链接。
- 所有的非叶子节点可以看成是索引部分,节点中仅含有其子树(根节点)中的最大(或最小)关键字。
- 除根节点外,其他所有节点中所含关键字的个数最少有
(注意:B 树中除根以外的所有非叶子节点至少有 棵子树)。
同时,B+ 树为了方便范围查询,叶子节点之间还用指针串联起来。
以下是一棵 B+ 树的典型结构:
B+ 树相比于 B 树的优势
由于索引节点上只有索引而没有数据,所以索引节点上能存储比 B 树更多的索引,这样树的高度就会更矮。树的高度越矮,磁盘寻道的次数就会越少。
因为数据都集中在叶子节点,而所有叶子节点的高度相同,那么可以在叶子节点中增加前后指针,指向同一个父节点的相邻兄弟节点,这样可以更好地支持查询一个值的前驱或后继,使连续访问更容易实现。
比如这样的 SQL 语句:select * from tbl where t > 10
,如果使用 B+ 树存储数据的话,可以首先定位到数据为 10 的节点,再沿着它的 next
指针一路找到所有在该叶子节点右边的叶子节点,返回这些节点包含的数据。
而如果使用 B 树结构,由于数据既可以存储在内部节点也可以存储在叶子节点,连续访问的实现会更加繁琐(需要在树的内部结构中进行移动)。
过程
与 B 树 类似,B+ 树的基本操作有查找,遍历,插入,删除。
查找
B+ 树的查找过程和 B 树类似。假设需要查找的键值是
一个实例:在如下这棵 B+ 树上查找 45。
先和根节点比较
因为根节点的键值比 45 要小,所以去往根节点的右子树查找
因为 45 比 30 大,所以要与右边的索引相比
右侧的索引也为 45,所以要去往该节点的右子树继续查找
然后就可以找到 45
需要注意的是,在查找时,若非叶子节点上的关键字等于给定值,并不终止,而是继续向下直到叶子节点。因此,在 B+ 树中,不管查找成功与否,每次查找都是走了一条从根到叶子节点的路径。其余同 B 树的查找类似。
查找一个键的代码如下:
遍历
B+ 树只在叶子节点的层级上就可以实现整棵树的遍历。从根节点出发一路搜索到最左端的叶子节点之后即可根据指针遍历。
插入
B+ 树的插入算法与 B 树的相近:
- 若为空树,创建一个叶子节点,然后将记录插入其中,此时这个叶子节点也是根节点,插入操作结束。
- 针对叶子类型节点:根据关键字找到叶子节点,向这个叶子节点插入记录。插入后,若当前节点关键字的个数小于
,则插入结束。否则将这个叶子节点分裂成左右两个叶子节点,左叶子节点包含前 个记录,右节点包含剩下的记录,将第 个记录的关键字进位到父节点中(父节点一定是索引类型节点),进位到父节点的关键字左孩子指针向左节点,右孩子指针向右节点。将当前节点的指针指向父节点,然后执行第 3 步。 - 针对索引类型节点(内部节点):若当前节点关键字的个数小于等于
,则插入结束。否则,将这个索引类型节点分裂成两个索引节点,左索引节点包含前 个 key,右节点包含 个 key,将第 个关键字进位到父节点中,进位到父节点的关键字左孩子指向左节点,进位到父节点的关键字右孩子指向右节点。将当前节点的指针指向父节点,然后重复这一步。
比如在下图的 B+ 树中,插入新的数据 10:
由于插入节点
而如下图的 B+ 树中,插入数据 4:
由于所在节点
- 大于左子树的最大值;
- 小于等于右子树的最小值。
综上,需要在父节点中新增索引 4 和两个指向新节点的指针。
更多的例子可以参考演示网站 BPlustree
插入一个键的代码如下:
删除
B+ 树的删除也仅在叶子节点中进行,当叶子节点中的最大关键字被删除时,其在非叶子节点中的值可以作为一个分界关键字存在。若因删除而使节点中关键字的个数少于
具体步骤如下:
- 首先查询到键值所在的叶子节点,删除该叶子节点的数据。
- 如果删除叶子节点之后的数据数量,满足 B+ 树的平衡条件,则直接返回。
- 否则,就需要做平衡操作:如果该叶子节点的左右兄弟节点的数据量可以借用,就借用过来满足平衡条件。否则,就与相邻的兄弟节点合并成一个新的子节点了。
在上面平衡操作中,如果是进行了合并操作,就需要向上修正父节点的指针:删除被合并节点的键值以及指针。
由于做了删除操作,可能父节点也会不平衡,那么就按照前面的步骤也对父节点进行重新平衡操作,这样一直到某个节点平衡为止。
可以参考 B 树 中的删除章节。
参考资料
本页面最近更新:2/3/2023, 12:00:00 AM,更新历史
发现错误?想一起完善? 在 GitHub 上编辑此页!
本页面的全部内容在 CC BY-SA 4.0 和 SATA 协议之条款下提供,附加条款亦可能应用