B树与B+树
B树
B树是一种平衡的多叉树,即查找路径不止两个。
通常我们说m阶的B树,它必须满足如下条件:
- 每个节点最多只有m个子节点。
- 每个非叶子节点(除了根)具有至少 m/2 子节点。
- 如果根不是叶节点,则根至少有两个子节点。
- 具有k个子节点的非叶节点包含 k-1 个键。
- 所有叶子都出现在同一水平,高度一致。
- 所有节点的键值是升序排列的。
m阶:指一个节点的子节点个数的最大值。
键:指节点中的键值。最大个数为 m-1。
查询
以上图的示例查询38为例:
- 从根节点开始,根节点的键值是22,22<38,所以进入根节点的右子节点。
- 节点的键值是[37,42], 37< 38 < 42,所以进入该节点的中间节点。
- 节点的键值是[38,39], 38=38,所以返回该值。
插入
以上图的示例插入40为例:
- 从根节点开始,根节点的键值是22,40>22,所以进入根节点的右子节点。
- 节点的键值是[37,42], 37< 40 < 42,所以进入该节点的中间节点。
- 节点的键值是[38,39], 39 < 40,由于该节点为叶节点,所以插入后面,键值为[38,39,40],保证键值升序排列。
- 由于该树为3阶,所以插入后,节点的键值个数超过2个,需要分裂。
- 将分裂的键插入到父节点。如果插入后父节点的键值个数超过2个,则需要分裂。重复该步骤。
- 插入后,根节点的键值个数超过2个,需要分裂并创建新的根节点。将分裂后的节点插入到新的根节点上。
分裂逻辑
- m阶的B树分裂时,需要将该满个数的节点的后 m/2 个键分裂出来创建新的节点
- 剩余的键值的最大值,也就是最后一个键值,需要插入到父节点中。
- 其他的键值作为原有的节点的键值。
具体可以查看《算法导论》第283页B树的分裂逻辑。
例如
上面的示例中[38,39,40]这个节点需要分裂,则将[40]创建新的节点,[39]提取到父节点中,[38]作为原有节点的键值。
如果是4阶的树,有节点[38,39,40,41]需要分裂,则将[40,41]创建新的节点,[39]提取到父节点中,[38]作为原有节点的键值。
插入后的树结构如下:
B+ 树
B+树是在B树基础上进一步改近得到的。B+树的非叶子节点不存储数据,只存储指关键字的指针,所有数据都保存在叶子节点。所有的叶子节点是从小到大排列的有序链表。
插入
- 如果为叶子节点则直接插入,如果非叶子节点,向下查找,直到找到叶子节点,然后插入。
- 如果插入后叶子节点的键值个数超过 m-1 ,则需要分裂。第 m/2 +1 值的键向上插入到父节点。
- 如果插入后父节点的键值个数超过 m-1 ,则需要分裂。第 m/2 值的键向上插入到父节点。
分裂逻辑
- 叶子节点
- 分裂的节点中左节点包含前 m/2 个值,右节点包含剩下的值。
- 第 m/2 + 1 个值的键向上插入到父节点。
- 将叶子节点的左右叶子节点的链接更新。
- 非叶子节点
- 分裂的节点中左节点包含前 m/2 个键,右节点包含 m/2 + 1 之后的键。
- 第 m/2 + 1 个键向上插入到父节点。
B+树的优势
- B+树查询更稳定,由于B+树的数据都存储于叶子节点上,所以每次查询都是相同的路径长度。
- B+树具备排序性,B+树中所有的叶子节点是一个有序的链表。
- B+树遍历更快,由于所有的数据都存储于叶子节点上,所以遍历树只需要遍历叶子节点的链表即可。