B-树以及B+树

B树是一种平衡的多路查找树,包括B-树以及B+树。一个节点可以拥有多个key以及多个孩子节点。一棵d+1阶(又称为branch factor)的B树满足如下条件:

  • 根节点非空,则至少两个孩子
  • 内部节点最少含有d个key,最多2*d个key

B-树

B-树的每个节点均存储key以及相应的value。B-树还具有如下特点:

  • 节点中key升序排列,并作为其子树的分离点(separator),因此m个key对应m+1个孩子节点
  • 节点中key的个数大于2*d就要对节点进行劈裂,位于中位数左边及右边的key分别构成新孩子节点,中位数插入父节点中
  • 叶节点位于同一层上,不含任何key的信息,实际没有指针指向叶节点(本文代码并不显式表示叶节点)
    《B-树以及B+树》

旋转(实际就是key的重分配)

B-树中的旋转操作不同于自平衡二叉搜索树,并不改变树的结构,仅仅更改key的位置。
**左旋:**将父节点中的key,替换对应子节点中预删除key,而将右兄弟节点中最小的key替换父节点中的key。例如上图中,删除76,那么父节点78替换76的位置,右兄弟中最小的79替换78的位置。
**右旋:**将父节点中的key,替换对应子节点中预删除key,而将左兄弟节点中最大的key替换父节点中的key。
旋转操作后,节点内的key需要重新排序。

合并

合并操作发生在预删除key所在节点以及左右兄弟包含的key的个数均等于d。那么删除一个key后,无法从左右兄弟中通过旋转操作补充key,只能与兄弟以及父节点中的分离点key合并,同时更新孩子节点的父节点。合并后的节点含有2*d个key。继续向上调整key的位置。
例如上图中,删除7,那么需要与右兄弟[20,25]合并,同时将父节点中的12拉回来,继续向上更新,则需要合并[13],[54]以及[69, 78]。

搜索

B-树从根节点开始,若节点中存在相同key,即break。
从根节点开始查找方法:

  1. 找到节点
  2. 在节点的key顺序表中查找,找到即返回True,反之找到对应的separator,继续搜索孩子节点

插入

B-树是自底向上生长的。

  1. 搜索到最底层节点,将key插入到节点中
  2. 若当前节点包含key的个数大于m-1,则劈裂派生出新节点,中位数插入当前节点的父节点。继续向上更新
  3. 若根节点包含key个数大于2*d,则构建新的根节点

删除

  1. 删除最底层节点M中的key:
    1. M中key的个数大于d,则直接删除,break
    2. key的个数小于等于d,则:
      1. 若左右兄弟节点中存在key的个数大于d,则rotate,break
      2. 若左右兄弟节点key的个数均小于d,则merge,继续向上更新
  2. 删除非最底层节点中的key,用以该key为分离点的右边孩子节点中最小的key替换,直到最低层节点转上1(例如预删除78,实际删除79)

B+树

B+树与B-树结构类似,对树的操作同样涉及旋转、合并等,主要区别如下:

  • 仅叶节点存储value,包含所有的key。叶节点中key升序排列,且各个叶节点有指向下一个叶节点的指针,构成单向链表
  • B+树内部节点仅存储key,每个key对应一个子节点,且key为相应子节点中最大key
    可以这样理解:一系列包含多个键值对的叶节点存储在单向链表中,因此可以采用顺序搜索。为了额外实现随机查找,同时引入”索引”,该索引就是key,存储在B树的内部以及根节点中。
    《B-树以及B+树》

旋转(实际就是key的重分配)

**左旋:**右兄弟中最小key替换预删除key,重新排序后逐步向上更新父节点中key。
**右旋:**左兄弟中最大key替换预删除key,重新排序后逐步向上更新父节点中key。

合并

与B-树类似,合并兄弟节点的key以及孩子节点。继续向上更新节点的key,若合并叶节点还需要更新指针。

搜索

**顺序搜索:**从head指针开始顺序搜索叶节点。
**随机搜索:**与B-树类似,不同是每次搜索必须到叶节点才返回(因为只有叶节点存储了value)。

插入

与B-树类似,需要注意的是如果插入的key是所在节点最大的key,那么就需要向上更新key。

删除

与B-树类似,需要注意的是,自底向上合并时,如果当前节点的父节点只有一个key,那么该父节点就是根节点,没有存在的必要,应以当前节点为新的根节点。

# B+树删除操作部分示例代码
def updateDelete(self, currentNode):
    P = currentNode.parent
    ind = P.children.index(currentNode)  # 获取当前节点在兄弟节点中的位置
    if ind == 0:  # 第一个,则无左兄弟
        SR = P.children[ind + 1]
        if len(SR.keys) > self.d:  # 右兄弟key个数大于d,则左旋
            self.leftRotate(currentNode, ind, P, SR)
        else:
            self.rightMerge(currentNode, ind, P, SR)
            if len(P.keys) > 1:
                self.updateDelete(P)
            else:  # 表明父节点为根节点,且只有一个key,那么孩子节点升级为根节点
                self.root = P.children[0]
    elif ind == len(P.children) - 1:  # 最后一个,则无右兄弟
        SL = P.children[ind - 1]
        if len(SL.keys) > self.d:
            self.rightRotate(currentNode, ind, P, SL)
        else:
            self.leftMerge(currentNode, ind, P, SL)
            if len(P.keys) > 1:
                self.updateDelete(P)
            else:
                self.root = P.children[0]
    else:
        SL = P.children[ind - 1]
        SR = P.children[ind + 1]
        if len(SL.keys) > self.d:
            self.rightRotate(currentNode, ind, P, SL)
        elif len(SR.keys) > self.d:
            self.leftRotate(currentNode, ind, P, SR)
        else:
            self.leftMerge(currentNode, ind, P, SL)

完整代码请参看我的GitHub

参考资料

https://en.wikipedia.org/wiki/B-tree
https://en.wikipedia.org/wiki/B+_tree
注:本文是个人的一些思考,代码等若有不当之处,还请指正。

    原文作者:B树
    原文地址: https://blog.csdn.net/slx_share/article/details/83048638
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞