B+树
1.基础概念
B+树是应数据库需求出现的一种B树的变形树,是一个m叉多路平衡查找树,B+树的查找、插入和删除操作都和 B树基本类似。
一颗 m
阶的 B+树:
- 结点的关键字个数与子树个数相同,即一个关键字对应一颗子树
- 根结点的关键字范围为 1 ≤ n ≤ m,但是非叶根结点必须至少有两颗子树
- 非根内部结点的关键字范围为 ⌈ m / 2 ⌉ \lceil{m/2}\rceil ⌈m/2⌉ ≤ n ≤ m
- 叶子结点的关键字范围为 ⌈ m / 2 ⌉ \lceil{m/2}\rceil ⌈m/2⌉ ≤ n ≤ m
- 叶子结点包含信息,非叶结点仅起到索引作用。叶子结点中出现所有的关键字,即使那些在非叶结点出现的关键字也会在叶子结点中出现
- 叶子结点中将关键字按大小顺序排列,相邻叶子结点按大小顺序链接起来
- 分支节点仅包含它子节点中的关键字最大值和指向子节点的指针
B+树比 B树更适合作为操作系统的文件索引:
- B+树的磁盘读写代价更低
- B+树节点小,一个节点可容纳更多的关键字因此,访问外存的次数少
- B+树的查询效率更加稳定。
- B+树任何关键字的查找必须走一条从根结点到叶子结点的路
2.查找
通常在 B+树中有两个头指针:一个指向根结点,另一个指向关键字最小的叶结点。我们可以对B+树进行两种查找运算:一种是从根结点开始的多路查找,另一种是从最小关键字开始的顺序查找。
在多路查找过程中,如果非叶结点上的关键字等于给定值,也不停止,而是继续沿着右指针向下,一直查到叶结点上有关键字等于给定值,然后返回对应的记录地址;如果查不到,则记录不存在。因此,在B+树中进行多路查找,无论成功与否,都会返回一条由根结点到叶子结点的路径。
对于记录总数K,B+树的叉树 N,整个 B+树的高度为
h
=
⌈
log
n
/
2
K
⌉
h = \lceil\log_{n/2} K\rceil
h=⌈logn/2K⌉。如果 K=1百万,N=20,那么 B+树高度为6,即在一个百万记录的文件中查找记录,也只需要访问6个树结点。即使每个树结点都占用一个磁盘块,最多也只需要7次 I/O
操作(6次结点磁盘和1次记录本身磁盘快)。
3.插入
B+树的插入在叶子结点上进行,叶子结点可能会因为插入操作而变得过大从而需要分裂,必须调整相关结点保证 B+ 树的平衡,否则查找操作的效率就会下降。
插入的规则如下:
- 查找给定值 V,如果插入值后的叶子结点关键字个数不超过 m,则将 V 插入该叶子结点,插入结束
- 如果叶子结点关键字个数等于 m,即插入之后叶子结点关键字为 m+1,则插入之后需要对该叶子结点进行分裂
- 以 ⌈ m / 2 ⌉ \lceil{m/2}\rceil ⌈m/2⌉ 位置的关键字作为分界,左侧 ⌈ ( m + 1 ) / 2 ⌉ \lceil(m+1)/2\rceil ⌈(m+1)/2⌉ 个关键字保留在原来的结点中,右侧 ⌊ ( m + 1 ) / 2 ⌋ \lfloor(m+1)/2\rfloor ⌊(m+1)/2⌋ 个关键字作为新结点,将两个结点的最大值和结点地址插入上层结点中
- 如果叶子结点分裂导致上层结点的关键字个数达到 m,同样对上层结点进行分裂。
4.删除
删除给定值为V的记录,需要先找到键值V所在的叶结点,将该键值的索引项删除。
删除的规则如下:
- 查找给定值 V,如果删除值 V 后的叶子结点关键字个数不少于 ⌈ m / 2 ⌉ \lceil{m/2}\rceil ⌈m/2⌉ ,则将 V 从该叶子结点中删除,删除结束。(如果是该叶子结点的最大值,可以不用删除对应上层结点的数值,这样不会影响到查找,以后的插入/删除可能会更新上层结点的值)
- 如果删除值V之后的叶子结点关键字个数小于 ⌈ m / 2 ⌉ \lceil{m/2}\rceil ⌈m/2⌉ ,删除之后需要对该叶子结点进行合并操作
- 查看右(左)兄弟结点中关键字个数是否大于 ⌈ m / 2 ⌉ \lceil{m/2}\rceil ⌈m/2⌉,如果是,从兄弟结点中借来最接近的关键字,并更新上层结点的对应数值
- 如果兄弟结点不够借,进行结点合并。将右侧结点的所有关键字都插入本结点中,删除变空的右侧结点。如果导致上层结点的子树个数小于 ⌈ m / 2 ⌉ \lceil{m/2}\rceil ⌈m/2⌉ ,则从上层结点的左侧兄弟结点借来一个最右侧的子树,然后对所有结点的数据和索引进行更新。
5.更新
对于B+树中数值的更新,不能简单地进行对应数值的更改,则是需要采取先删除再插入的策略,以保持B+树索引结构的更新
6.示例
参考资料
数据结构(C语言版)第二版 —— 清华大学出版社
数据结构精讲与习题详解(C语言版)第二版 —— 清华大学出版社