我们之前已经对 平衡搜索二叉树有了一定的了解,学习了两种树——AVL树 和 红黑树,下面介绍一下B树
数据库中使用的就是B+树(和B树原理是一样的后面会单独介绍),那我们为什么不使用的AVL树和红黑树作为数据库的索引呢 ?原因是每次查询都是一次磁盘IO(数据库中的数据是存在file system中,每次查询的时候是需要读到内存中的),而磁盘IO中耗时最长的就是将磁盘读写磁头的定位,而每次查询都大概率都会移动磁头。所以如何降低IO的次数(实际上就是降低树的高度),就是降低总体查询时间。B树就是再次基础上做出了两点改进:
首先我们要了解一下B树的度:即每个B树节点最多可以有M个子节点,这个M就是B树的度
- 每个结点的值(索引) 都是按递增次序排列存放的,并遵循左小右大原则。
- 对于所有节点来说:
关键字的数量始终=子节点的数量-1(这一点不论B树什么时候都必须满足)
- 对于根节点:
-关键字的数量满足[1,M-1]
- 子节点的数量满足[2,M]
- 对于除了根节点以外的其他节点
-关键字的数量满足[Math.ceil(M/2) , M-1] //Math.ceil代表向上取整
- 子节点的数量满足[Math.ceil(M/2) , M] //Math.ceil代表向上取整
- B树所有叶子节点位于同一层
我们下面都以度为3的B树作为样例作为讨论:
那么度为3的B树的节点吗,满足什么特性呢?
对于度为3的B树根节点和非根节点的要求是一样的,但是当度M>=5
时候就会不一样了,读者可以自己计算一下。
以下所有的都是以度为3的B树为示例
然后我们用less_key_number
表示一个节点关键字最少的数量,这里为ceil(3/2)-1=1
注意:less+key_number
的数量始终小于M/2
——这里M/2
不取整
节点数据结构的伪代码:
int cur_sz=0; //记录当前key的数量
K array_k[M]; //Key的数组
BTreeNode<K, M>* array_node[M+1]; // 存储子节点的数组
这里我们把关键字数组和子节点数组都多开了一个值,实际上还是符合上面B树的规则,只是方便后面插入时候将插入和调整过程分离
注意
例如下标为i的array_k[i]
的左节点为:array_node[i]
右节点为:array_node[i-1]
插入过程的思路
注意: 我们在插入之前B树的所有节点的关键字都是满足数量的要求的,这是插入的前提
判断发现不满足B树的节点的关键字数量这时候我们就要进行分裂操作:
less_key_number
+1+M-1-less_key_number
,(这三部分我们后面分别用部分1、部分2、部分3代称)如果大伙感兴趣可以计算一下上面三个部分每个部分的关键字数量都不超过M/2
(不论M是奇数还是偶数)。因为父节点中插入了一个新的关键字,可能导致父节点的关键字数量超出M-1所以需要继续向上遍历判断
ok这个最简单的例子看完了我们来看一个复杂一点的例子:
假设我们现在有了一个如下的B树,现在我们要插入关键字:3
注意
上文在介绍树节点的数据结构的时候不论是array_k
还是array_node
的时候都比B树规定的多开了一个空间,到这里你应该能体会到为什么了吧?
目的就是为了将插入和分裂两个过程解耦,不然插入和分裂弄在一块就会很麻烦
B树的删除思路如下
下面我将举几个例子来带大家深入对删除规则的理解:
以下面这个B树为例:通过删除不同的关键字来将删除思路中的不同情况加以说明
和前面一样前两步依然一样:找到关键字所在节点,删除关键字
删除完了之后,我们发现不满足关键字数量>=less_key_number
于是我们优先看看兄弟节点是否有多余的关键字
找了一圈发现兄弟也没有多余的(注意这里的情况有一个潜台词:所有兄弟节点的关键字数量等于less_key_number
,如果兄弟节点关键字数量大于less_key_number
一定可以借给删除节点,如果小于less_key_number
则不符合B树的定义),这时只能向爸爸(父节点)借,由于父节点被接走了一个关键字此时还必须满足关键字数量==子节点数量-1
,所以删除节点还需要和一个兄弟节点合并。
我们现在来计算一下合并后的新节点的关键字数量:less_key_number-1
(删除节点的关键字数量)+ 1(从父节点借的关键字)+less_key_number
(兄弟节点的关键字数量:上面论证过为什么一定等于这个数)=2*less_key_number
。而上文我们已经知道less_key_number
2*less_key_number
一定小于等于M-1
注意:在合并的过程中array_k
数组和array_node
数组两个都需要调整,你叶子节点调不调整看不出来,但是中间节点不调整就会出问题
合并之后:
注意虽然这里的cur节点一个关键字没有,他依然满足关键字数量==子节点数量-1
情况四
上面删除的关键字无一例外的都是叶子节节点的关键字,如果删除的关键字不在叶子节点怎么办?这个思路也很简单——找删除关键字所在节点左子树的最大值 或 右子树的最小值 替换,替换完之后又变成了叶子节点删除。这个在红黑树和AVL树中都有体现就不细说了
模拟实现了一下,通过了自己编的一些测试用例,不保证对
代码