B树-插入

B树系列文章

1. B树-介绍

2. B树-查找

3. B树-插入

4. B树-删除

 

插入

根据B树的以下两个特性

  • 每一个结点最多有m个子结点
  • 有k个子结点的非叶子结点拥有 k − 1 个键

可以得出,B树每个结点存放键的数量是有上限的是m-1,因此插入操作可能导致结点“溢出”。

插入操作的重点和难点在于结点“溢出”后的再平衡操作

 

假设有一棵3阶B树,如下图所示。

 

通过给这棵3阶B树插入键值53,来分析B树插入键值的过程。

首先,参考查找的步骤,最终定位到53应该插入到Node21结点的最后

但是这是一个3阶B树,每个结点拥有键的最大数量为2,因此插入53会导致Node21"溢出"

由于Node21结点“溢出”,需要对Node21进行拆分,拆分方法如下

  • 从该结点的原有键和新的键中选择出中位数,这个中位数作为分隔值
  • 小于这一中位数的键放入左边结点,大于这一中位数的键放入右边结点,中位数作为分隔值。
  • 分隔值被插入到父结点中,这可能会造成父结点分裂,分裂父结点时可能又会使它的父结点分裂,以此类推。如果没有父结点(这一结点是根结点),就创建一个新的根结点(增加了树的高度)。

下图是对Node21进行拆分的结果,Node21分裂成两个新的结点和分隔值53,53需要插入父结点中

将分隔值插入父结点中,形成Node2结点,原来的Node21结点被拆分为新的Node21和Node22结点

可以看出来,此时Node2结点键的数量达到了阶数,即达到“溢出”条件了

因此,需要继续对Node2进行拆分

 

对Node2进行拆分,中间值55提升到root结点,原来的Node2结点拆分成Node2和Node3两个新结点

同样的,此时root结点也“溢出”了,需要对root结点进行拆分

对root结点的拆分,最终造成B树高度的增加

 

对根结点进行拆分,55被提升,需要创建新的根结点存放键55,键值40和70分别构成新的两个儿子结点,分别是Node1和Node12

此时B数重新平衡了。

 

这里总结下

所有的插入都从根结点开始。要插入一个新的键,首先搜索这棵树找到新键应该被添加到的对应结点。将新键插入到这一结点中的步骤如下:

如果结点拥有的键数量小于最大值,那么有空间容纳新的键。将新键插入到这一结点,且保持结点中键有序。

否则的话这一结点已经满了,将它平均地拆分成两个结点:

从该结点的原有键和新的键中选择出中位数

小于这一中位数的键放入左边结点,大于这一中位数的键放入右边结点,中位数作为分隔值。

分隔值被插入到父结点中,这可能会造成父结点分裂,分裂父结点时可能又会使它的父结点分裂,以此类推。如果没有父结点(这一结点是根结点),就创建一个新的根结点(增加了树的高度)。

 

这里是插入的代码

/**
* 插入key
* 时间复杂度为O(logn)
**/
func (bTreeNode *BTreeNode) intert(key int, m int) ([]*BTreeNode, int) {
    // 找到第一个不比key小的,注意leaf的数量比key数量多1
    idx := 0
    for idx < bTreeNode.keyNum && key > bTreeNode.keyList[idx] {
        idx++
    }

    // BTreeNode已有该key
    if idx < bTreeNode.keyNum && bTreeNode.keyList[idx] == key {
        return nil, 0
    }

    if bTreeNode.isLeaf { // 叶子结点
        // idx及idx后面元素往后移动
        if idx < m-1 {
            copy(bTreeNode.keyList[idx+1:], bTreeNode.keyList[idx:])
        }
        bTreeNode.keyList[idx] = key
        bTreeNode.keyNum += 1
    } else { // 非叶子结点
        // 判断结点是否要满了
        // 先往上提
        children, midKey := bTreeNode.leafList[idx].intert(key, m)
        if children != nil {
            // idx是midKey的插入位置,idx后面元素往后移动
            if idx < m-1 {
                copy(bTreeNode.keyList[idx+1:], bTreeNode.keyList[idx:])
                copy(bTreeNode.leafList[idx+2:], bTreeNode.leafList[idx+1:])
            }
            bTreeNode.keyList[idx] = midKey
            bTreeNode.leafList[idx] = children[0]
            bTreeNode.leafList[idx+1] = children[1]
            bTreeNode.keyNum += 1
        }
    }

    if bTreeNode.keyNum == m { // 节点key数量超了
        // 从中间将该结点一分为2
        mid := bTreeNode.keyNum / 2
        midKey := bTreeNode.keyList[mid]

        // 左儿子
        leftNode := createNode(m, mid, bTreeNode.isLeaf)
        copy(leftNode.keyList, bTreeNode.keyList[:mid])
        copy(leftNode.leafList, bTreeNode.leafList[:mid+1])

        // 右边儿子
        rightNode := createNode(m, m-mid-1, bTreeNode.isLeaf)
        copy(rightNode.keyList, bTreeNode.keyList[mid+1:])
        copy(rightNode.leafList, bTreeNode.leafList[mid+1:])

        return []*BTreeNode{leftNode, rightNode}, midKey
    }
    return nil, 0
}

/** 插入key
* 时间复杂度O(logn)
**/
func (bTree *BTree) Insert(key int) {
    children, midKey := bTree.root.intert(key, bTree.m)
    if children != nil {
        root := createNode(bTree.m, 1, false)
        root.keyList[0] = midKey
        root.leafList[0] = children[0]
        root.leafList[1] = children[1]
        bTree.root = root
    }
}

 

 
posted @ 2022-07-18 23:07  bulldozer  阅读(1505)  评论(0编辑  收藏  举报