B+树
一、什么是 B+ 树?
B+ 树是 B-树的变种。
其结构类似这样:
1 2 3 4 5 6 7 8 9 10 11 12
| ____ ___ ___ | 24 | | | | -------------------------------------------------------- | | ____ ____ ___ ____ ___ ___ | 15 | 20 | | | 33 | | | | | -------------------------------------------- ----------------------- | | | | | ____ ____ ___ ____ ____ ___ ____ ____ ___ ____ ____ ___ ____ ____ ___ | 10 | 12 | | <=> | 15 | 18 | | <=> | 20 | 21 | | <=> | 30 | 31 | | <=> | 34 | 38 | |
|
B+ 树和 B-树的主要区别在于:
- 内部节点只是索引,只有叶子节点才保存数据
- 叶子节点之间构成了一条双向链表
- 叶子节点包含了所有关键码值,而内部节点仅包含用于索引的关键码值
B+ 树和 B-树的区别主要在于叶子节点,它们的内部节点基本是一样的。
二、为什么用 B+ 树?
- B+ 树特别适合范围查询,因为叶子节点之间是双向连接,可以顺着双向链表寻找数据
- B+ 树只会在叶子节点上插入和删除数据,相对于 B-树来说,实现会简单一些
三、如何实现 B+ 树?
和 B-树差不多,B+ 树也包含了几种操作:
- 检索元素:在树中搜索指定的元素
- 插入元素:往树中插入新的元素
- 删除元素:从树中删除指定的元素
其中,插入和删除会对 B+ 树的结构产生影响,稍微麻烦。
3.1 检索元素
B+ 树的检索和 B-树的差不多:
- 基于多路查找,找到对应的节点
- 需要一直找到叶子节点,因为只有叶子节点有数据
比如,上面的 B+ 树中,要寻找元素 20
,它的搜索路线就是:
1 2 3 4 5 6 7 8 9 10 11 12
| ____ ___ ___ | 24 | | | | -------------------------------------------------------- | | ____ ____ ___ ____ ___ ___ | | 20 | | | | | | | | -------------------------------------------- ----------------------- | | | | | ____ ____ ___ ____ ____ ___ ____ ____ ___ ____ ____ ___ ____ ____ ___ | | | | <=> | | | | <=> | 20 | | | <=> | | | | <=> | | | |
|
注意:虽然内部节点也有 20
这个关键码值,但是它没有保存数据,仅仅是作为索引,所以还是要一直搜索到叶子节点。
这个检索过程对应的伪代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| private V get(BPTNode<K, V> root, K key) { int index = root.findIndex(key); if (root.isLeaf()) { if (key.equals(root.getKey(index))) { BPTLeaf<K, V> leaf = (BPTLeaf<K, V>) root; return leaf.getValue(index); } else { return null; } } else { return get(root.getChild(index), key); } }
|
B+ 树的检索要一直到达叶子节点才行。
3.2 插入元素
B+ 树和 B-树一样:
B-树节点的分裂实际只有 2 种情况,但是 B+ 树的叶子节点分裂会有点特殊:
- 叶子节点分裂后,新的左右叶子节点会保留所有的关键码值
- 分裂出来的父节点的关键码值,等于右叶子节点中的最小关键码值
这是因为,B+ 树的数据都在叶子中保存,不能将其上溢到父节点中,要保持这种规则。
由于 B+ 树叶子节点和内部节点的分裂不同,所以会分为 4 种情况:
- 叶子节点空间足够,直接插入元素
- 叶子节点空间不足,1 个节点分裂成 3 个节点,执行上溢操作
- 内部节点空间足够,直接插入元素
- 内部节点空间不足,1 个节点分裂成 3 个节点,执行上溢操作
这 4 种情况的效果如下。
3.2.1 叶子节点-直接插入
当叶子节点空间足够时,可以直接插入新元素:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| ____ ____ ___ | 12 | | |
| 插入 20 v
____ ____ ___ | 12 | 20 | |
| 插入 9 v
____ ____ ____ | 9 | 12 | 20 |
|
此时的叶子节点空间足够时,直接插入元素就行了。
3.2.2 叶子节点-分裂上溢
当叶子节点空间不足时,再插入新元素,会导致节点分裂,产生上溢:
1 2 3 4 5 6 7 8 9 10 11 12 13
| ___ ____ ____ | 9 | 12 | 20 |
| 插入 25 v
____ ___ ___ | 20 | | | | --------------------- | | ___ ____ ___ ____ ____ ____ | 9 | 12 | | <=> | 20 | 25 | |
|
节点空间不足以插入新元素,所以 1 个节点会分裂成 3 个节点。其中,
- 叶子分裂后,新的叶子节点会保留所有的关键码值
[9, 12]
、[20, 25]
- 父节点的关键码值,等于右叶子节点的最小关键码值
[20]
- 叶子节点之间,还需要补充上双向链接
在叶子节点分裂上溢这一点上,B+ 树和 B-树有所不同。
3.2.3 内部节点-直接插入
为了保持 B+ 树的高度平衡,叶子节点会将分裂后的父节点,往上插入之前的父节点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| ____ ___ ___ | 20 | | | | ------------------------ | | ___ ____ ____ ____ ____ ____ | 9 | 12 | 18 | <=> | 20 | 25 | |
| 插入 10 v
____ ____ ___ | 12 | 20 | | | ----------------------------------------------- | | | ___ ____ ____ ____ ____ ____ ____ ____ ____ | 9 | 10 | | <=> | 12 | 18 | | <=> | 20 | 25 | |
|
左子节点 [9, 12, 18]
插入新元素 10
后会产生上溢,12
会上溢父节点 [20]
中。
因为内部节点 [20]
的空间足够,所以 12
直接插入即可。
3.2.4 内部节点-分裂上溢
节点上溢后,如果父节点空间也满了,那么父节点也会出现分裂上溢:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| ____ ____ ____ | 12 | 20 | 45 | | ------------------------------------------------------------------ | | | | ___ ___ ____ ____ ____ ____ ____ ____ ____ ____ ____ ____ | 8 | 9 | 10 | | 12 | 18 | | | 20 | 25 | | | 45 | 85 | |
| 插入 11 v
____ ____ ____ | 20 | | | | ------------------------------------------------------------ | | ____ ____ ___ ____ ___ ___ | 10 | 12 | | | 45 | | | | | ---------------------------------------------- ------------------------- | | | | | ___ ___ ____ ____ ____ ____ ____ ____ ____ ____ ____ ____ ____ ____ ____ | 8 | 9 | | <=> | 10 | 11 | | <=> | 12 | 18 | | <=> | 20 | 25 | | <=> | 45 | 85 | |
|
插入元素 11
之后,会传递性地导致节点 [8, 9, 10]
和节点 [12, 20, 45]
都产生分裂上溢。
注意,内部节点的分裂和叶子节点是不同的:
- 内部节点分裂后,子节点不会出现和父节点一样的关键码值
- 新分裂父节点的关键码值,等于插入新元素后的中间关键码值
这种分裂上溢可以一直持续到根节点,最后可能会导致 B+ 树提升一层(如上)。
上述的 4 种情况,对应的伪代码差不多是这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| private BPTNode<K, V> put(BPTNode<K, V> root, K key, V value) { int index = root.findIndex(key); if (root.isLeaf()) { BPTLeaf<K, V> leaf = (BPTLeaf<K, V>) root; return leaf.add(key, value); } else { BPTInternal<K, V> node = (BPTInternal<K, V>) root; BPTNode<K, V> child = node.getChild(index); child = put(child, key, value);
return node.overflow(index, child); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| public BPTNode<K, V> add(K key, V value) { int index = findIndex(key);
if (!this.isFull()) { insertEntry(index + 1, new Entry<>(key, value)); return this; }
return splitNodes(index, new Entry<>(key, value)); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| public BPTNode<K, V> overflow(int index, BPTNode<K, V> node) { if (size + node.size() < m) { for (Entry<K, V> entry : node.entries()) { insertEntry(++index, entry); } return this; }
return splitNodes(index, node); }
|
3.3 删除元素
删除元素,和插入一样,由于叶子节点的特殊性,所以实际上会有 6 种情况:
- 叶子节点元素足够,直接删除
- 叶子节点元素不足,看兄弟节点是否可以借一个过来(和内部节点不同)
- 叶子节点兄弟节点不能借,那就和兄弟合并,删除父节点(和内部节点不同)
- 内部节点元素足够,直接删除
- 内部节点元素不足,看兄弟节点是否可以借一个过来(和叶子节点不同)
- 内部节点兄弟节点不能借,那就和兄弟合并,删除父节点(和叶子节点不同)
这几种情况如下。
3.3.1 叶子节点-直接删除
如果是叶子节点,且删除元素后,元素数量仍然处于半满状态,则可以直接删除:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| ____ ___ ___ | 20 | | | | ----------------------- | | ___ ____ ___ ____ ____ ____ | 9 | 12 | | <=> | 20 | 25 | |
| 删除 20 v
____ ___ ___ | 20 | | | | ----------------------- | | ___ ____ ___ ____ ____ ____ | 9 | 12 | | <=> | 25 | | |
|
叶子节点 [20, 25]
删除 20
以后,元素数量依旧满足半满状态,则可以直接删除并返回。
注意:直接删除叶子节点元素 20
后,并不会改变父节点的关键码值 20
。
3.3.2 叶子节点-从兄弟借
如果删除元素后,元素数量少于一半,不满足要求,则先看一下兄弟节点能不能借个元素过来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| ____ ___ ___ | 20 | | | | ----------------------- | | ___ ____ ___ ____ ____ ____ | 9 | 12 | | <=> | 25 | | |
| 删除 25 v
____ ___ ___ | 12 | | | | ----------------------- | | ___ ____ ___ ____ ____ ____ | 9 | | | <=> | 12 | | |
|
右子节点 [25]
删除 25
以后,元素数量不满足要求了,找兄弟节点 [9, 12]
借一个元素。
和 B-树的旋转不同,B+ 树叶子节点的借用是这样的:
- 叶子节点直接从兄弟节点借一个元素过来
- 同时还要更新叶子节点对应的父节点关键码值
删除 25
以后,除了借 12
过来,还要更新父节点的关键码值为 12
。
上面是从左兄弟借,从右兄弟借也是一样的,要更新父节点的关键码值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| ____ ___ ___ | 20 | | | | ----------------------- | | ___ ____ ___ ____ ____ ____ | 9 | | | <=> | 20 | 25 | |
| 删除 9 v
____ ___ ___ | 25 | | | | ----------------------- | | ____ ___ ___ ____ ____ ____ | 20 | | | <=> | 25 | | |
|
3.3.3 叶子节点-合并下溢
如果兄弟节点借不了元素,那就将左子节点 + 右子节点
合并在一起,并删除父节点元素
:
1 2 3 4 5 6 7 8 9 10 11 12 13
| ____ ___ ___ | 25 | | | | ----------------------- | | ____ ___ ___ ____ ____ ____ | 20 | | | <=> | 30 | | |
| 删除 30 v
____ ____ ___ | 20 | | |
|
注意:叶子节点的合并下溢,并不包括父节点元素值,只是左右叶子兄弟合并,因为内部节点不是数据节点。
3.3.4 内部节点-直接删除
叶子节点合并下溢后,父节点需要删除对应的父节点元素,如果父节点空间足够,就可以直接删除:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| ____ ____ ___ | 12 | 20 | | | ----------------------------------------------- | | | ___ ____ ____ ____ ____ ____ ____ ____ ____ | 9 | 10 | | <=> | 12 | 18 | | <=> | 20 | | |
| 删除 20 v
____ ____ ___ | 12 | | | | ------------------------ | | ___ ____ ____ ____ ____ ___ | 9 | 10 | | <=> | 12 | 18 | |
|
删除 20
以后,会造成叶子节点合并下溢,父节点元素 20
被删除。
由于父节点 [12, 20]
空间足够,删除 20
后依旧满足要求,所以直接删除即可。
3.3.5 内部节点-从兄弟借
如果内部节点删除时,发现空间不足,则先考虑从兄弟节点借一个元素:
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
| ____ ____ ____ | 20 | | | | ------------------------------------------------------------ | | ____ ____ ___ ____ ___ ___ | 10 | 12 | | | 45 | | | | | ---------------------------------------------- ------------------------- | | | | | ___ ___ ____ ____ ____ ____ ____ ____ ____ ____ ____ ____ ____ ____ ____ | 8 | 9 | | <=> | 10 | | | <=> | 12 | 18 | | <=> | 20 | 25 | | <=> | 85 | | |
| 删除 85 v
____ ____ ____ | 12 | | | | ----------------------------------------------- | | ____ ____ ___ ____ ___ ___ | 10 | | | | 20 | | | | | ------------------------ ------------------------ | | | | ___ ___ ____ ____ ____ ____ ____ ____ ____ ____ ____ ____ | 8 | 9 | | <=> | 10 | | | <=> | 12 | 18 | | <=> | 20 | 25 | |
|
删除元素 85
,会造成叶子节点 [20, 25]
、[85]
合并,并删除父节点元素 45
。
但是内部节点 [45]
由于空间不足,就需要从兄弟节点 [10, 12]
借一个元素过来才行。
不过,内部节点的借用和叶子节点的借用不同:
- 内部节点借用,实际上是旋转操作,将兄弟节点旋到父节点,父节点旋到当前节点
上面借用的元素 12
,实际会旋转到父节点,然后父节点 20
再旋转到右子节点中。
3.3.6 内部节点-合并下溢
如果内部节点的兄弟节点借不了元素,只能将父子节点进行合并,产生下溢:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| ____ ____ ____ | 12 | | | | ----------------------------------------------- | | ____ ____ ___ ____ ___ ___ | 10 | | | | 20 | | | | | ------------------------ ------------------------ | | | | ___ ___ ____ ____ ____ ____ ____ ____ ____ ____ ____ ____ | 8 | 9 | | <=> | 10 | | | <=> | 12 | 18 | | <=> | 20 | | |
| 删除 20 v
____ ____ ___ | 10 | 12 | | | ------------------------------------------- | | | ___ ___ ____ ____ ____ ____ ____ ____ ____ | 8 | 9 | | <=> | 10 | | | <=> | 12 | 18 | |
|
删除元素 20
,会造成叶子节点 [12, 18]
、[20]
合并,并删除父节点元素 20
。
删除内部节点元素 20
时,由于其空间不足,且兄弟节点无法借用,只能合并父子节点,产生下溢了。
注意,内部节点的合并和叶子节点的合并不同:
- 内部节点的合并下溢,包括了
父节点 + 左子节点 + 右子节点
即上面的父子节点 [12]
、[10]
、[20]
会合并起来,形成一个新节点。
下溢和上溢一样,也可能会持续下溢,一直持续到根节点,最终导致树的层级下降。
上述情况的伪代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| private BPTNode<K, V> remove(BPTNode<K, V> root, K key) { int index = root.findIndex(key); if (root.isLeaf()) { BPTLeaf<K, V> leaf = (BPTLeaf<K, V>) root; if (key.equals(root.getKey(index))) { return leaf.delete(index); } else { return leaf; } } else { BPTInternal<K, V> node = (BPTInternal<K, V>) root; remove(node.getChild(index), key); return node.underflow(index); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public BPTNode<K, V> delete(K key, V value) { int index = findIndex(key);
if (this.isEnough()) { removeEntry(index); return this; }
if (borrowLeft(index) || borrowRight(index)) { return this; }
return mergeNodes(index); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public BPTNode<K, V> underflow(int index) { if (this.isEnough()) { removeEntry(index); return this; }
if (borrowLeft(index) || borrowRight(index)) { return this; }
return mergeNodes(index); }
|
总结
- 性质
- 内部节点只是索引,只有叶子节点才保存数据
- 叶子节点之间构成了一条双向链表
- 叶子节点包含了所有关键码值,而内部节点仅包含用于索引的关键码值
- 检索
- 插入
- 叶子节点空间足够,直接插入
- 叶子节点空间不足,分裂上溢(父节点关键码值等于右叶子节点的最小关键码值)
- 内部节点空间足够,直接插入
- 内部节点空间不足,分裂上溢(节点的关键码值都不一样)
- 删除
- 叶子节点空间足够,直接删除
- 叶子节点空间不足,从兄弟借(移动)
- 叶子节点兄弟节点无法借用,合并左右叶子节点,删除父元素,执行下溢(仅合并兄弟叶子节点)
- 内部节点空间足够,直接删除
- 内部节点空间不足,从兄弟借(旋转)
- 内部节点兄弟节点无法借用,合并父子节点,执行下溢(合并父子节点)
参考
《数据结构与算法分析(第三版)》
https://www.cnblogs.com/nullzx/p/8729425.html
附录
B+ 树接口
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
|
public interface BPTree<K extends Comparable<K>, V> {
V get(K key);
void put(K key, V value);
void remove(K key);
}
|
B+ 树实现
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 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
|
public class BPTreeImpl<K extends Comparable<K>, V> implements BPTree<K, V> {
private BPTNode<K, V> root;
private final int m;
public BPTreeImpl(int m) { this.m = m; }
@Override public V get(K key) { return get(root, key); }
private V get(BPTNode<K, V> root, K key) { if (root == null) { return null; }
int index = root.findIndex(key); if (root.isLeaf()) { if (key.equals(root.getKey(index))) { BPTLeaf<K, V> leaf = (BPTLeaf<K, V>) root; return leaf.getValue(index); } else { return null; } } else { return get(root.getChild(index), key); } }
@Override public void put(K key, V value) { root = put(root, key, value); }
private BPTNode<K, V> put(BPTNode<K, V> root, K key, V value) { if (root == null) { BPTLeaf<K, V> leaf = new BPTLeaf<>(m); return leaf.add(key, value); }
int index = root.findIndex(key); if (root.isLeaf()) { BPTLeaf<K, V> leaf = (BPTLeaf<K, V>) root;
if (key.equals(leaf.getKey(index))) { leaf.setValue(index, value); return leaf; }
return leaf.add(key, value); } else { BPTInternal<K, V> node = (BPTInternal<K, V>) root; BPTNode<K, V> child = node.getChild(index); child = put(child, key, value);
return node.overflow(index, child); } }
@Override public void remove(K key) { root = remove(root, key); if (root != null && root.isEmpty()) { root = root.firstChild(); } }
private BPTNode<K, V> remove(BPTNode<K, V> root, K key) { if (root == null) { return null; }
int index = root.findIndex(key); if (root.isLeaf()) { BPTLeaf<K, V> leaf = (BPTLeaf<K, V>) root; if (key.equals(root.getKey(index))) { return leaf.delete(index); } else { return leaf; } } else { BPTInternal<K, V> node = (BPTInternal<K, V>) root; remove(node.getChild(index), key); return node.underflow(index); } }
}
|
B+ 节点定义
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 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363
|
public abstract class BPTNode<K extends Comparable<K>, V> {
protected final int m;
protected final int threshold;
protected final Object[] elements;
protected int size;
public BPTNode(int m) { this.m = m; this.threshold = half(m); elements = new Object[m]; elements[0] = new Entry<K, V>(null, null); size = 0; }
public int size() { return size; }
protected int half(int m) { return (m + 1) / 2 - 1; }
public boolean isFull() { return size == m - 1; }
public boolean isEmpty() { return size == 0; }
public boolean isLegal() { return size >= threshold; }
protected boolean canBorrow() { return size > threshold; }
public abstract boolean isLeaf();
protected List<Entry<K, V>> entries() { List<Entry<K, V>> entries = new ArrayList<>(size); for (int i = 1; i <= size; i++) { entries.add(getEntry(i)); } return entries; }
public List<K> keys() { List<K> keys = new ArrayList<>(size); for (int i = 1; i <= size; i++) { keys.add(getKey(i)); } return keys; }
public K getKey(int index) { return getEntry(index).key; }
public List<BPTNode<K, V>> children() { List<BPTNode<K, V>> children = new ArrayList<>(size + 1); for (int i = 0; i <= size; i++) { children.add(getChild(i)); } return children; }
public BPTNode<K, V> firstChild() { return getChild(0); }
public BPTNode<K, V> lastChild() { return getChild(size); }
public BPTNode<K, V> getChild(int index) { return getEntry(index).pointer; }
public void setChild(int index, BPTNode<K, V> node) { getEntry(index).pointer = node; }
public int findIndex(K key) { return 1 + binaryle(keys(), key); }
protected int binaryle(List<K> keys, K key) { int size = keys.size(); int l = 0, r = size - 1; while (l <= r) { int m = l + (r - l) / 2; K k = keys.get(m); if (k.compareTo(key) <= 0) { if (m == size - 1 || keys.get(m + 1).compareTo(key) > 0) { return m; } l = m + 1; } else { r = m - 1; } } return -1; }
protected Entry<K, V> getEntry(int index) { return (Entry<K, V>) elements[index]; }
protected void setEntry(int index, Entry<K, V> entry) { Entry<K, V> oldEntry = getEntry(index); if (oldEntry == null || index == 0) { throw new IllegalStateException(String.format("index: %d, size: %d", index, size)); }
elements[index] = entry; entry.pointer = oldEntry.pointer; }
protected void addEntry(Entry<K, V> entry) { if (isFull()) { throw new IllegalStateException(String.format("size: %d", size)); }
elements[++size] = entry; }
protected void insertEntry(int index, Entry<K, V> entry) { if (isFull() || index == 0) { throw new IllegalStateException(String.format("index: %d, size: %d", index, size)); }
if (size >= index) { System.arraycopy(elements, index, elements, index + 1, size - index + 1); elements[index] = entry; size++; } else { elements[index] = entry; size = index; } }
protected void removeEntry(int index) { if (isEmpty() || index == 0) { throw new IllegalStateException(String.format("index: %d, size: %d", index, size)); }
if (size > index) { System.arraycopy(elements, index + 1, elements, index, size - index); } elements[size] = null; size--; }
@Override public String toString() { String[] s = new String[size]; for (int i = 1; i <= size; i++) { s[i - 1] = elements[i].toString(); } return "[" + String.join(", ", s) + "]"; }
public static class Entry<K extends Comparable<K>, V> {
K key;
V value;
BPTNode<K, V> pointer;
public Entry(K key, V value) { this.key = key; this.value = value; pointer = null; }
@Override public String toString() { return "{" + key + " : " + value + "}"; } }
}
|
B+ 树内部节点
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 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255
|
public class BPTInternal<K extends Comparable<K>, V> extends BPTNode<K, V> {
public BPTInternal(int m) { super(m); }
@Override public boolean isLeaf() { return false; }
public BPTNode<K, V> overflow(int index, BPTNode<K, V> node) { if (node == null || node.isEmpty()) { return this; }
BPTNode<K, V> cur = getChild(index); if (cur == node) { return this; }
if (size + node.size() < m) { setChild(index, node.firstChild()); for (Entry<K, V> entry : node.entries()) { insertEntry(++index, entry); } return this; }
return splitNodes(index, node); }
private BPTNode<K, V> splitNodes(int index, BPTNode<K, V> newNode) { setChild(index, newNode.firstChild());
int newSize = size + newNode.size(); List<Entry<K, V>> allEntries = new ArrayList<>(newSize); List<Entry<K, V>> curEntries = entries(); for (int i = 0; i < index && i < size; i++) { allEntries.add(curEntries.get(i)); } allEntries.addAll(newNode.entries()); for (int i = index; i < size; i++) { allEntries.add(curEntries.get(i)); }
int mid = newSize / 2; BPTInternal<K, V> root = new BPTInternal<>(m); for (int i = mid; i <= mid; i++) { root.addEntry(allEntries.get(i)); } BPTInternal<K, V> left = new BPTInternal<>(m); for (int i = 0; i < mid; i++) { left.addEntry(allEntries.get(i)); } BPTInternal<K, V> right = new BPTInternal<>(m); for (int i = mid + 1; i < newSize; i++) { right.addEntry(allEntries.get(i)); }
left.setChild(0, firstChild()); right.setChild(0, root.lastChild()); root.setChild(0, left); root.setChild(root.size, right); return root; }
public BPTNode<K, V> underflow(int index) { BPTNode<K, V> cur = getChild(index); if (cur == null || cur.isLegal()) { return this; }
BPTNode<K, V> left = null; if (index > 0) { left = getChild(index - 1); if (left != null && left.canBorrow()) { return borrowLeft(index); } } BPTNode<K, V> right = null; if (index < size) { right = getChild(index + 1); if (right != null && right.canBorrow()) { return borrowRight(index); } } if (left == null && right == null) { return this; }
return mergeNodes(left != null ? index : index + 1); }
private BPTNode<K, V> borrowLeft(int index) { BPTNode<K, V> left = getChild(index - 1); BPTNode<K, V> right = getChild(index); if (left.isLeaf()) { Entry<K, V> leftEntry = left.getEntry(left.size); left.removeEntry(left.size); right.insertEntry(1, leftEntry); setEntry(index, new Entry<>(leftEntry.key, null)); setChild(index, right); } else { Entry<K, V> parentEntry = getEntry(index); Entry<K, V> leftEntry = left.getEntry(left.size); BPTNode<K, V> leftRight = left.lastChild();
right.insertEntry(1, parentEntry); right.setChild(1, right.firstChild()); left.removeEntry(left.size); setEntry(index, leftEntry); setChild(index, right); right.setChild(0, leftRight); } return this; }
private BPTNode<K, V> borrowRight(int index) { BPTNode<K, V> right = getChild(index + 1); BPTNode<K, V> left = getChild(index); if (right.isLeaf()) { Entry<K, V> rightEntry = right.getEntry(1); right.removeEntry(1); left.addEntry(rightEntry); Entry<K, V> newRightEntry = right.getEntry(1); setEntry(index + 1, new Entry<>(newRightEntry.key, null)); setChild(index + 1, right); } else { Entry<K, V> parentEntry = getEntry(index + 1); Entry<K, V> rightEntry = right.getEntry(1); BPTNode<K, V> rightLeft = right.firstChild();
left.addEntry(parentEntry); right.setChild(0, right.getChild(1)); right.removeEntry(1); setEntry(index + 1, rightEntry); setChild(index + 1, right); left.setChild(left.size, rightLeft); } return this; }
private BPTNode<K, V> mergeNodes(int index) { BPTNode<K, V> left = getChild(index - 1); BPTNode<K, V> right = getChild(index);
if (!left.isLeaf()) { Entry<K, V> parentEntry = getEntry(index); left.addEntry(parentEntry); }
left.setChild(left.size, right.firstChild()); for (Entry<K, V> entry : right.entries()) { left.addEntry(entry); }
removeEntry(index);
if (left.isLeaf()) { BPTLeaf<K, V> leftLeaf = (BPTLeaf<K, V>) left; BPTLeaf<K, V> rightLeaf = (BPTLeaf<K, V>) right; BPTLeaf<K, V> rightNext = rightLeaf.getNext(); if (rightNext != null) { rightNext.setPrev(leftLeaf); } leftLeaf.setNext(rightNext); } return this; }
}
|
B+ 树叶子节点
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 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
|
public class BPTLeaf<K extends Comparable<K>, V> extends BPTNode<K, V> {
private BPTLeaf<K, V> prev;
private BPTLeaf<K, V> next;
public BPTLeaf(int m) { super(m); }
@Override public boolean isLeaf() { return true; }
public BPTNode<K, V> add(K key, V value) { int index = findIndex(key);
if (!this.isFull()) { insertEntry(index + 1, new Entry<>(key, value)); return this; }
return splitNodes(index, new Entry<>(key, value)); }
private BPTNode<K, V> splitNodes(int index, Entry<K, V> newEntry) { List<Entry<K, V>> curEntries = entries(); curEntries.add(index, newEntry); int newSize = curEntries.size(); int mid = newSize / 2;
BPTNode<K, V> root = new BPTInternal<>(m); Entry<K, V> entry = curEntries.get(mid); root.addEntry(new Entry<>(entry.key, null)); BPTLeaf<K, V> left = new BPTLeaf<>(m); for (int i = 0; i < mid; i++) { left.addEntry(curEntries.get(i)); } BPTLeaf<K, V> right = new BPTLeaf<>(m); for (int i = mid; i < newSize; i++) { right.addEntry(curEntries.get(i)); } root.setChild(0, left); root.setChild(root.size, right);
left.prev = this.prev; if (this.prev != null) { this.prev.next = left; } left.next = right; right.prev = left; if (this.next != null) { this.next.prev = right; } right.next = this.next; this.prev = this.next = null; return root; }
public BPTNode<K, V> delete(int index) { removeEntry(index); return this; }
public V getValue(int index) { return getEntry(index).value; }
public void setValue(int index, V value) { getEntry(index).value = value; }
public BPTLeaf<K, V> getPrev() { return prev; }
public BPTLeaf<K, V> getNext() { return next; }
protected void setPrev(BPTLeaf<K, V> prev) { this.prev = prev; }
protected void setNext(BPTLeaf<K, V> next) { this.next = next; }
}
|