该库提供一个允许插入重复关键码的B+树实现,该实现方案参考了一种处理B+树重复键值的方法,文献中描述了一种除溢出页之外的处理重复关键码方法,由于该文献并未详述实现细节,因此在具体实现上细节上会有一定差异,本文档将给出该版本实现中的B+树定义与具体的实现细节阐述。
如果此前从未了解过B+树,建议优先学习B+树wiki,通常B+树的实现中约定不引入重复关键码,或者使用溢出页处理重复关键码,当前实现并非采用前两种方案实现,因此在定义上会与wiki中有一定差异。
当前库主要用于了解与学习B+树,若需实际使用当前版本实现请自行完成各项指标的测试工作。
出于可移植性考虑,B+树的核心实现中尽少使用面向对象,尽量使用方法封装。
D.以下定义针对于m阶B+树:
- 内部节包含分支和关键码,最多有
m
个分支和m-1
个关键码,除根节点外最少有ceil(m/2)
个分支和ceil(m/2)-1
个关键码。 - 叶子节包含关键码和对应值,点最多有
m-1
个关键码,除根节点外最少有ceil(m/2)-1
个关键码。 - 根节点最少有
2
个分支和1
个关键码。 - 内部节点中第
k
个关键码定存放的是其第k+1
个子节点为根的子树中第一次出现的”新“的数据关键码。
DS.定义的实现细节:
- 为了简化定义4的实现,为所有内部节点设置一个哨兵关键码,但进行具体关键码数量计算时并不计入哨兵关键码。
- 为了实现空关键码的定义,实现中不允许用户插入空的关键码。
- 为了明确返回value的语义,实现中不允许用户插入空的value。
内部节点(InternalNode):
- parent:指向父节点。
- keys:关键码列表,按照升序列排列,首个关键码是哨兵关键码,因此数量上与分支相同,根据DS.1除根节点外最多包含
m
个,最少包含ceil(m/2)
个。 - children:子节点列表,
children[i]
是keys[i]
的右子树,即D4中描述的k
和k+1
关系。
叶子节点(LeafNode):
- parent:指向父节点。
- prev:指向前一个叶子节点。
- next:指向后一个叶子节点。
- keys:关键码列表,按照升序列排列,除根节点外最多包含
m-1
个,最少包含ceil(m/2)-1
个。 - values:值列表,
keys[i]
与value[i]
对应。
B+树:
- m:阶次。
- root:根节点。
- parent指针为空:是根节点。
- parent指针非空:不是根节点。
- 内部节点(对内部节点而言keys数量即为分支数):keys数量大于
m
。 - 叶子节点:keys数量大于
m-1
。
- 内部节点且为根:keys数量小于2。
- 内部节点且非根:keys数量小于
ceil(m/2)
。 - 叶子节点且为根:keys数量小于1。
- 叶子节点且非根:keys数量小于
ceil(m/2)-1
。
对于内部节点而言需要通过输入key与索引值的比较定位key所在子树。
从索引0处开始查找,直到找到首个等于key或者最后一个小于key的索引位置r,children[r]即为key所在子树。
从索引0处开始查找的隐含意义是如果keys[0]就大于key,那么就向children[0]深入,因为这是全树的最左侧。
对于可能存在重复关键码的B+树而言,首次定位到叶子节点并不意味着结束。
- key小于等于当前叶子节点末尾关键码查找结束,否则执行步骤2。
- 通过next跳转到下一叶子节点重复步骤1。
- 执行A4获得首个叶子节点。
- 执行A5获得目标叶子节点。
通过改算法找到的叶子节点是可能包含该key最靠左的节点。
查找keys中查找小于key秩最大的元素的下标。
该算法作用于内部节点,是为了更新输入内部节点所对应父节点中的关键码值而设计的。
该算法性质来自于《一种处理B+树重复键值的方法》,内部节点第k
个索引键值等于对应子内部节点从小到大第一个非空索引键值,如果所有都是空键值则第k
个索引键值也是空。
- 当前节点为根则算法结束。
- 通过性质决定是否更新。
尽管通过细致的判断能够更为迅速地更新叶子节点所对应父节点位置的关键码,但出于简单性考虑这里将给出一个简明而通用的算法,这样一来会损失部分性能,但可以保持KISS原则。该算法同样依赖于性质,内部节点第k
个索引键值等于对应子内部节点从小到大第一个非空索引键值,如果所有都是空键值则第k
个索引键值也是空。
叶子节点前一叶子节点的最后一个关键码称作prevLastKey
,当前叶子节点首个关键码称作firstKey
,当前关键码对应父节点位置的关键码称作keyInParent
。
- 如果当前节点为空,更新
keyInParent
为空,算法结束,否则进入步骤2。 prevLastKey == firstKey
,搜索首个不等于firstKey
的关键码foundKey
,更新keyInParent
为foundKey
,算法结束。prevLastKey < firstKey
,更新keyInParent
为firstKey
,算法结束。
大体类似常规B+树上溢解决思路,注意索引的更新即可。
大体类似常规B+树下溢解决思路,注意索引的更新即可。
- 执行A6获得key可能存在的叶子节点。
- 执行A7获取下标
r
。 - 查看
r+1
位置(可能越界)是否为目标key,是则返回对应value,否则查找失败。
一旦通过当前算法找到目标节点,若key也存在,就能保证其查找到的key是相同值里最靠左的。
- 若B+树为空,新建节点赋值给
root
,插入结束,否则进入步骤2。 - 执行A6与A7定位插入点。
- 执行插入。
- 插入后可能引起当前叶子节点的
keyInParent
违反性质,尝试检查并更新。 - 执行A10尝试解决上溢。
- 若B+树为空,返回空算法结束,否则进入步骤2。
- 执行A6与A7定位要删除点,若要删除键值不存在,返回空算法结束,否则进入步骤3。
- 执行删除。
- 删除操作可能引起当前叶子
keyInParent
和后一个叶子节点的nextKeyInParent
违反性质,尝试检查并更新。 - 执行A11尝试解决下溢。