因为这几天看数据库方索引面的内容,所以自己写了一个BTree的Demo,加深一下自己对BTree的理解。因为只是为了学习,所以只涉及了基本功能的实现,不涉及并发和事务以及优化。
本文中的BTree完全是参照算法导论中的描述实现的,导论中的Btree在插入和删除删除过程中,都会提前检验节点是否可能出现不符合BTree规则的状态,提前将这些可能出现的状态消除掉。这样做的好处是,避免了插入删除过程中可能出现的递归回溯问题,使得在并发过程中加锁更加的简单。但是因为提前预判的缘故也会带来一些不必要的合并和分裂子树的消耗。至于这种避免递归回溯的实现方式,是否比递归回溯保证Tree正确性的方式更为高效?我觉得是个挺有意思的问题,在我现在看来如果是并发情况下,由于需要加锁,提前预判会比递归回溯有更好的效果。而单线程情况下则递归回溯会比提前预判有更好的效果。
#### 1. 基本概念
B 树是为了磁盘或其它存储设备而设计的一种多叉平衡查找树,一颗B树具有以下性质:
1 |
|
关于B树的具体概念可以参照其他blog或者《算法导论》里面都有很详细的描述,并且在https://www.cs.usfca.edu/~galles/visualization/BTree.html上有递归回溯方式的图形化展示动画,本文重点讲一下实现的过程。
2. 基本实现
2.1 插入操作
按照《算法导论》的描述,当root->keyNum == MinChild*2-1需要特殊处理:生成新的root节点,向上生长使得BTree的高度加一。除此之外所有的插入操作都由insertNotFull来完成,并且在递归插入的路径上,不断的检测节点的 keyNum是否等于MinChild * 2 - 1。如果相等,为了防止后面插入过程中splitChildNode使得父节点出现keyNum == MinChild * 2的情况发生向上的递归检验,提前将节点拆分,并继续递归删除。
1 |
|
2.1 删除操作
和插入操作一样,如果root->keyNum==1,需要做一些特判和处理。安照《算法导论》中的描述:BTREE的高度降低只会发生在root。如果树root->keyNum==1,可能存在子树合并使得root节点的keyNum==0不满足条件的情况,因此提前合并使得的Btree高度下降,避免回溯。
1 |
|
在特判完了root节点以后,还要实现在叶子节点和中间节点的删除操作:
-
如果待删除的key在叶子节点上找到,因为在上一步的递归删除过程中保证了next->node的keynum > minChild-1所以可以直接直接删除,无需回溯检查。
- 如果待删除的key在中间节点上找到,且左右孩子存在一个keyNum大于MinChild - 1的节点,则找该key的前驱或者后继(newvalue),并用newvalue的值覆盖当前的key以后, 继续向下递归删除newvalue。(不直接删除newvalue的原因是,无法保证删除以后的树仍合法,如leaf节点的keyNum<MinChild - 1) 如果key值附近的左右孩子都为MinChild - 1,则将key和与其相邻的左右child节点合并,再继续向下删除。执行这一步的目的是保证向下删除过成中delete递归所经过的路径上,所有节点的keynum数量>MinChild - 1. 这样在后面的节点合并时不会出现父节点的keynum < MinChild - 1导致回溯的情况。(MergeChild候需要向父节点的一个key),removeAtNode(),的代码在后面给出。
- 如果key在内部节点中没有找到,且nextNode的keynum大于MinChild - 1,可以直接继续删除操作否则需要借一个节点或者合并两个子节点来保证nextNode的keyNum > MinChild-1,removeMergeOrBorrow的代码在后面给出。
1 |
|
3 查找操作
查找操作相对比较简单就直接给出实现代码:
1 |
|
3. 完整代码
上述过程只是简单的描述了一下插入和删除的实现,但是里面的很多实现的细节并没有给出,比如合并和分裂子树,向左右兄弟借一个节点,虽然这些实现不是很难,但也有很多细节需要考虑。因此在本文的最后贴上这些方法的实现的代码,完整代码和测试用例可访问:https://github.com/yunxiao3/dataStructure/tree/master/BTree,如有问题可以将其发送到me@jackdu.cn。
1 |
|
removeAtNode和removeMergeOrBorrow的实现:
1 |
|