B树的思路以及go语言实现【算法】

一、定义

  B树B-tree,B-树其实就是B树,英文名balanced tree。一棵m阶B树(balanced tree of order m)是一棵平衡的m路搜索树。它或者是空树,或者是满足下列性质的树:

  1、根结点至少有两个子女;
  2、每个非根节点所包含的关键字个数 j 满足:┌m/2┐ - 1 <= j <= m - 1;
  3、除根结点以外的所有结点(不包括叶子结点)的度数正好是关键字总数加1,故内部子树个数 k 满足:┌m/2┐ <= k <= m ;
  4、所有的叶子结点都位于同一层。
  5、B树是搜索树(有序)

二、思路实现

  B树设计初衷就是为了查找,所以查找是最简单的,相反插入、删除反而异常繁琐,每次操作都会多次使用查找。这里我这要分析B树中如何插入及删除,。

插入操作,一般搜索树而言,插入就是找到相应的位置,直接加入节点,B树为了数据能够快速被查找,在插入的时候不仅要插入到相应的位置,还需要根据情况来调整树的高度以及宽度,尽可能使树高度下降。

插入分为以下几种:

  1、若插入的节点中含有的关键词小于m-1,则正常添加关键词到节点即可

  2、若插入的节点中含有关键词等于m-1(这里不存在大于m-1,因为一旦达到m就会分裂),则需要向上分裂(这个是需要递归到根节点

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
//r为根节点指针,key为插入关键词,value是插入的数据
func InsertTreeNode(r *node, key int16, value int16) int16 {
    if r.indexesNum == 0 { // 根节点为空
        fmt.Println("root is empty node", r)
        r.indexes = make([]int16, M+1, M+1)
        r.vals = make([]int16, M+1, M+1)
        r.children = make([]*node, M+1, M+1)
        var i int16
        for i = 0; i <= M; i++ {
            r.indexes[0] = 0
            r.vals[i] = 0
            r.children[i] = nil
        }
        r.indexesNum++
        r.indexes[1] = key
        r.vals[1] = value
    } else {
        // Find out if this node exists
        res, pos, state := FindOfNode(r, key)//res是查找到的节点,pos找到插入的位置
        if state == TRUE {// 每次查找都需要判断是否存在数据
            fmt.Println("This n node is exist!")
            return ERROR
        }
        if state == FALSE {// 不存在关键词,表示可以插入
            res.indexesNum++
            var i int16
            for i = res.indexesNum; i > pos; i-- {
                res.indexes[i] = res.indexes[i-1]
                res.children[i] = res.children[i-1]
                res.vals[i] = res.vals[i-1]
            }
            res.indexes[pos] = key
            res.vals[pos] = value
            res.children[pos] = nil
            if res.indexesNum == M {//这里就是达到节点能储存的关键词上线就需要向上分裂
                splitEdge(res)
            }
            renewParent(r) //每次分裂会导致节点位置变化,需要重新指向父级指针
        }
    }
    return OK
}

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
/**
Split Nodes<br>向上分裂
*/
func splitEdge(n *node) int16 {
    var n1, n2 *node
    var i, pos int16
    if n.parent == nil {
        n1 = MakeNode()
        n2 = MakeNode()
        n1.indexesNum = M / 2
        n2.indexesNum = M - M/2 - 1
        n1.parent = n
        n2.parent = n
        for i = 0; i <= M; i++ {
            n1.children[i] = nil
            n1.indexes[i] = 0
            n1.vals[i] = 0
            n2.children[i] = nil
            n2.indexes[i] = 0
            n2.vals[i] = 0
        }
        for i = 0; i <= M/2; i++ {
            n1.children[i] = n.children[i]
            n1.indexes[i] = n.indexes[i]
            n1.vals[i] = n.vals[i]
        }
        n2.children[0] = n.children[M/2+1]
        for i = M/2 + 2; i <= M; i++ {
            n2.children[i-M/2-1] = n.children[i]
            n2.indexes[i-M/2-1] = n.indexes[i]
            n2.vals[i] = n.vals[i]
        }
        n.indexesNum = 1
        n.children[0] = n1
        n.children[1] = n2
        n.indexes[1] = n.indexes[M/2+1]
        n.vals[1] = n.vals[M/2+1]
        for i = 2; i <= M; i++ {
            n.indexes[i] = 0
            n.vals[i] = 0
            n.children[i] = nil
        }
    } else {
        pos = WhichChildNode(n)
        for i = n.parent.indexesNum; i > pos; i-- {
            n.parent.indexes[i+1] = n.parent.indexes[i]
            n.parent.vals[i+1] = n.parent.vals[i]
            n.parent.children[i+1] = n.parent.children[i]
        }
        n.parent.indexesNum++
        n.parent.indexes[pos+1] = n.indexes[M/2+1]
        n.parent.vals[pos+1] = n.vals[M/2+1]
        n2 = MakeNode()
        n.parent.children[pos+1] = n2
        for i = 0; i <= M; i++ {
            n2.indexes[i] = 0
            n2.vals[i] = 0
            n2.children[i] = nil
        }
        n2.indexesNum = M - M/2 - 1
        n2.parent = n.parent
        n2.children[0] = n.children[M/2+1]
        for i = M/2 + 2; i <= M; i++ { // Initialization n2
            n2.indexes[i-M/2-1] = n.indexes[i]
            n2.vals[i-M/2-1] = n.vals[i]
            n2.children[i-M/2-1] = n.children[i]
        }
        n.indexesNum = M / 2
        for i = M/2 + 1; i <= M; i++ { // Initialization n1
            n.indexes[i] = 0
            n.vals[i] = 0
            n.children[i] = nil
        }
        if n.parent.indexesNum == M {
            splitEdge(n.parent)
        }
    }
    return OK
}

  上面插入还算简单,接着就是删除操作,对于删除方式,首先分为叶子跟非叶子节点:
  非叶子节点:如果该关键字所在的结点不是最下层的非叶子结点,则先需要把此关键字与它在B树中后继(最近右边子树)对换位置,即以指针Pi所指子树中的最小关键字Y代替Ki,然后在相应的结点中删除Y。

  叶子节点:(非叶子节点需要替换到叶子节点,然后在进行下面的操作)

  1、若该关键字Ki所在结点中的关键字个数大于等于m/2,则可以直接从该结点中删除该关键字和相应指针即可。(这个最简单)
  2、若该关键字Ki所在结点中的关键字个数小于m/2,可以分为以下几种:
    1)考虑左兄弟节点中的关键词个数,是否大于等于m/2(这个不需要递归到根节点,因为节点高度没变化)
    2)考虑右兄弟节点中的关键词个数,是否大于等于m/2(这个不需要递归到根节点,因为节点高度没变化)
    3)兄弟都没多余的关键词个数,那就合并(这个需要递归到根节点
      ① 左兄弟(存在的情况)+ 自身(删除后剩余的关键词)+与父级节点中的一个关键词 = 合并到左兄弟节点中
        ② 右兄弟(存在的情况)+ 自身(删除后剩余的关键词)+与父级节点中的一个关键词 = 合并到自身节点中
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//r为根节点,key为删除的关键词
func DeleteTreeNode(r *node, key int16) int16 {
    if r == nil {
        return ERROR
    }
    // 找出存在的关键词
    res, pos, state := FindOfNode(r, key)
    if state == FALSE {
        return FALSE
    }
    if state == TRUE {
        if res.children[0] == nil { // 如果为叶子节点
            for i := pos; i < res.indexesNum; i++ {
                res.indexes[i] = res.indexes[i+1]
                res.vals[i] = res.vals[i+1]
                res.children[i] = res.children[i+1]
            }
            res.indexesNum--
            MergeTreeNode(res)
        } else {//非叶子节点需要替换后继子节点中最小叶子节点中的最小关键词
            rn := FindMinKey(res, res.children[pos], pos)
            var i int16
            for i = 1; i < rn.indexesNum; i++ {
                rn.indexes[i] = rn.indexes[i+1]
                rn.vals[i] = rn.vals[i+1]
                rn.children[i] = rn.children[i+1]
            }
            rn.indexesNum--
            MergeTreeNode(rn)
        }
        renewParent(r)
    }
    return ERROR
}

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/**
合并操作
*/
func MergeTreeNode(n *node) int16 {
    p := n.parent
    if p == nil || p.indexesNum <= 0 {
        if n.indexesNum <= 0 {
            if n.children[0] != nil {
                root = *n.children[0]
            }
        } else {
            root = *n
        }
        root.parent = nil
        return TRUE
    }
    if n.indexesNum >= M/2 {
        return TRUE
    }
    // Borrow it from your brother
    pos := WhichChildNode(n)
    // Borrow from brother left
    var i, j int16
    for i = pos - 1; i >= 0; i-- {
        if p.children[i] != nil && p.children[i].indexesNum > M/2 { // 左兄弟节点有多的关键词
            for j = i; j < pos; j++ {
                MoveLeftToRight(p.children[j], p.children[j+1], p, j+1)
            }
            return TRUE
        }
    }
 
    // Borrow from brother right
    for i = pos + 1; i <= p.indexesNum; i++ {
        if p.children[i] != nil && p.children[i].indexesNum > M/2 {// 右兄弟节点有多关键词
            for j = i; j > pos; j-- {
                MoveRightToLeft(p.children[j], p.children[j-1], p, j)
            }
            return TRUE
        }
 
    }
    // 兄弟节点都没多,只能合并
    if pos > 0 {// 我向左兄弟合并
        if p.children[pos] == nil || p.children[pos-1] == nil {
            return ERROR
        }
        MergeParentBotherLeft(p.children[pos], p.children[pos-1], p, pos)
        MergeTreeNode(p)// 递归操作
    } else if pos < p.indexesNum { // 右兄弟向我合并
        if p.children[pos] == nil || p.children[pos+1] == nil {
            return ERROR
        }
        MergeParentBotherRight(p.children[pos+1], p.children[pos], p, pos+1)
        MergeTreeNode(p)
    }
    return TRUE
}

 三、总结

  起初只是以为B树是简单搜索树的一种,本人也是经常刷水题的程序猿,然而却花费了近一周时间才做出来(我也不知道有没有问题,测试3到10阶),最难的是删除合并,这个思路太卡了,借鉴了各种大神图文讲解,代码研究,终于写出来了。后面有空还会研究B+树、B*树以及其他算法结构。下面附上完整代码。

  

 

posted @   Auler  阅读(974)  评论(0编辑  收藏  举报
编辑推荐:
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!
点击右上角即可分享
微信分享提示