什么是B+树
B+ 树是自平衡树的高级形式,其中所有值都位于叶子节点。 在学习B+树之前要理解的一个重要概念是多级索引。在多级索引中,索引的索引创建如下图。它使访问数据更容易、更快。
![]() |
---|
使用 B+ 树的多级索引 |
B+树的性质
- 所有的叶子都在同一水平线上。
- 根至少有两个孩子。
- 除根之外的每个节点最多可以有 m 个子节点和至少 m/2 个子节点。
- 每个节点最多可以包含 m - 1 个键和至少 ⌈m/2⌉ - 1 个键。
B树和B+树的比较
数据指针仅存在于 B+树的叶节点中,而数据指针可以存在于B树的内部、叶或根节点中。叶子节点在B树上不相互连接,而它们在B+树上相互连接。B+树上的操作比 B树上的操作快。
![]() |
---|
B树 |
![]() |
---|
B+树 |
B+树的操作
B+树的搜索
搜索操作
按照以下步骤在B+序树中搜索的数据为k.
- 从根节点开始。将 k 与根节点的键进行比较 [k1, k2, k3,……km - 1].
- 如果 k < k1,转到根节点的左子节点。
- 否则如果 k == k1, 继续和k2比较. 如果k < k2, 则k 介于k1和 k2之间. 所以搜索k2的左子节点.
- 如果 k > k2, 继续与 k3, k4,…km -1 重复第 2 步和第 3 步。
- 重复以上步骤,直到到达叶子节点。
- 如果 k 存在于叶节点中,则返回 true 否则返回 false。
搜索示例
让我们在下面的 B+ 树上搜索 k = 45
![]() |
---|
B+树 |
- 将 k 与根节点进行比较。
|
| | —- | | 在根处找不到 k |
- 由于 k > 25,去右孩子。
|
| | —- | | 转到根的右侧 |
- 将 k 与 35 进行比较。由于 k > 30,将 k 与 45 进行比较。
|
| | —- | | 没有找到 |
- 由于 k ≥ 45,所以继续搜索右孩子。
|
| | —- | | 向右走 |
- k 被发现。
|
| | —- | | 找到 k |
搜索时间复杂度
如果在节点内部实现线性搜索,则总复杂度为 Θ(logt n).
如果使用二分查找,则总复杂度为 Θ(log2t.logt n).
B+树的插入
将元素插入B+ 树包括三个主要事件:搜索适当的叶子、插入元素和平衡/分裂树。
插入约束
插入操作在将元素插入 B+ 树之前,必须牢记这些约束:
- 根至少有两个孩子。
- 除根之外的每个节点最多可以有 m 个孩子和至少 m/2 的孩子。
- 每个节点最多可以包含 m - 1 个键和至少 ⌈m/2⌉ - 1 键。
插入算法
按照以下步骤插入元素。
- 由于每个元素都插入到叶节点中,因此请转到相应的叶节点。
- 将key插入叶节点。
案例一:叶子节点未满
- 如果叶子节点未满,则将key按升序插入叶子节点。
案例二:叶子节点已满
- 如果叶子节点已满,则将key按升序插入叶子节点,并按以下方式平衡树。
- 在 m/2 位置断开节点。
- 将m/2位置的key添加到父节点。
- 如果父节点已满,请执行步骤 2 至 3。
插入示例
要插入的元素是 5,15, 25, 35, 45。
插入 5 |
| | —- | | 插入 5 |
插入 15 |
| | —- | | 插入 15 |
插入 25。(在 15 位置分裂成2个节点。 将key 15添加到父节点。) |
| | —- | | 插入 25 |
插入 35(在 25位置分裂成2个节点。 将key 25添加到父节点。) |
| | —- | | 插入 35 |
插入 45 |
| | —- | | 插入 45 |
插入复杂度
- 时间复杂度:Θ(t.logt n)
B+树的删除
删除 B+ 树上的元素包括三个主要事件:搜索要删除的键所在的节点,删除键并在需要时平衡树。下溢是一种情况,当节点中的键数少于它应该保存的最小键数时。
删除约束
在进行以下步骤之前,必须了解有关度数为m(例如m=3)的 B+树的这些事实。
- 一个节点最多可以有 m 个子节点。(即 3)
- 一个节点最多可以包含 m - 1 个键。(即 2)
- 一个节点应该有最少的 ⌈m/2⌉ 个子节点。(即 2)
- 一个节点(除了根节点)应该包含最少的⌈m/2⌉ - 1 个键。(即 1)
在删除键时,我们还必须注意内部节点(即索引)中存在的键,因为这些值在 B+ 树中是多余的。搜索要删除的key,然后按照以下步骤操作。
删除算法
案例一:删除的键仅在叶子节点
要删除的键仅存在于叶子节点中,而不存在于索引(或内部节点)中。它有两种情况:
- 节点中的键数超过最少key数量的约束,只需删除key。
|
| | —- | | 从B+树中删除 40 |
- 节点中的key数量低于最小约束。删除key并从直接兄弟中借用key。将兄弟节点的中间键添加到父节点。
|
| | —- | | 从B+树中删除 5 |
案例二:删除的键也在内部节点
要删除的key即在叶子节点,也存在于内部节点中。然后我们必须从内部节点中删除它们。这种情况有以下几种情况。
- 如果节点中的键数量高于最小数量的约束,只需从叶子节点中删除键并从内部节点中删除键即可。用中序后继填充内部节点中的空白空间。
|
| | —- | | 从B+树中删除 45 |
- 如果节点中的键数低于最小数量的约束,则删除该键并从其直接兄弟(通过父级)借用一个键。用借用的键填充索引(内部节点)中创建的空白空间。
|
| | —- | | 从B+树中删除 35 |
- 这种情况类似于案例二(1),但在这里,在直接父节点上方生成空白空间。删除键后,将空白空间(不一定为空)与其兄弟合并。用中序后继节点填充祖父节点中的空白空间。
|
| | —- | | 从B+树中删除 25 |
案例三:删除节点导致平衡打破
在这种情况下,树的高度会缩小。有点复杂。从下面的树中删除 55 会导致这种情况。可以在下面的插图中理解。
![]() |
---|
从 B 树中删除 55 |
B+树的应用
- 多级索引
- 树上更快的操作(插入、删除、搜索)
- 数据库索引
B+树的实现
// Searching on a B+ tree in Java
import java.util.*;
public class BPlusTree {
int m;
InternalNode root;
LeafNode firstLeaf;
// Binary search program
private int binarySearch(DictionaryPair[] dps, int numPairs, int t) {
Comparator<DictionaryPair> c = new Comparator<DictionaryPair>() {
@Override
public int compare(DictionaryPair o1, DictionaryPair o2) {
Integer a = Integer.valueOf(o1.key);
Integer b = Integer.valueOf(o2.key);
return a.compareTo(b);
}
};
return Arrays.binarySearch(dps, 0, numPairs, new DictionaryPair(t, 0), c);
}
// Find the leaf node
private LeafNode findLeafNode(int key) {
Integer[] keys = this.root.keys;
int i;
for (i = 0; i < this.root.degree - 1; i++) {
if (key < keys[i]) {
break;
}
}
Node child = this.root.childPointers[i];
if (child instanceof LeafNode) {
return (LeafNode) child;
} else {
return findLeafNode((InternalNode) child, key);
}
}
// Find the leaf node
private LeafNode findLeafNode(InternalNode node, int key) {
Integer[] keys = node.keys;
int i;
for (i = 0; i < node.degree - 1; i++) {
if (key < keys[i]) {
break;
}
}
Node childNode = node.childPointers[i];
if (childNode instanceof LeafNode) {
return (LeafNode) childNode;
} else {
return findLeafNode((InternalNode) node.childPointers[i], key);
}
}
// Finding the index of the pointer
private int findIndexOfPointer(Node[] pointers, LeafNode node) {
int i;
for (i = 0; i < pointers.length; i++) {
if (pointers[i] == node) {
break;
}
}
return i;
}
// Get the mid point
private int getMidpoint() {
return (int) Math.ceil((this.m + 1) / 2.0) - 1;
}
// Balance the tree
private void handleDeficiency(InternalNode in) {
InternalNode sibling;
InternalNode parent = in.parent;
if (this.root == in) {
for (int i = 0; i < in.childPointers.length; i++) {
if (in.childPointers[i] != null) {
if (in.childPointers[i] instanceof InternalNode) {
this.root = (InternalNode) in.childPointers[i];
this.root.parent = null;
} else if (in.childPointers[i] instanceof LeafNode) {
this.root = null;
}
}
}
}
else if (in.leftSibling != null && in.leftSibling.isLendable()) {
sibling = in.leftSibling;
} else if (in.rightSibling != null && in.rightSibling.isLendable()) {
sibling = in.rightSibling;
int borrowedKey = sibling.keys[0];
Node pointer = sibling.childPointers[0];
in.keys[in.degree - 1] = parent.keys[0];
in.childPointers[in.degree] = pointer;
parent.keys[0] = borrowedKey;
sibling.removePointer(0);
Arrays.sort(sibling.keys);
sibling.removePointer(0);
shiftDown(in.childPointers, 1);
} else if (in.leftSibling != null && in.leftSibling.isMergeable()) {
} else if (in.rightSibling != null && in.rightSibling.isMergeable()) {
sibling = in.rightSibling;
sibling.keys[sibling.degree - 1] = parent.keys[parent.degree - 2];
Arrays.sort(sibling.keys, 0, sibling.degree);
parent.keys[parent.degree - 2] = null;
for (int i = 0; i < in.childPointers.length; i++) {
if (in.childPointers[i] != null) {
sibling.prependChildPointer(in.childPointers[i]);
in.childPointers[i].parent = sibling;
in.removePointer(i);
}
}
parent.removePointer(in);
sibling.leftSibling = in.leftSibling;
}
if (parent != null && parent.isDeficient()) {
handleDeficiency(parent);
}
}
private boolean isEmpty() {
return firstLeaf == null;
}
private int linearNullSearch(DictionaryPair[] dps) {
for (int i = 0; i < dps.length; i++) {
if (dps[i] == null) {
return i;
}
}
return -1;
}
private int linearNullSearch(Node[] pointers) {
for (int i = 0; i < pointers.length; i++) {
if (pointers[i] == null) {
return i;
}
}
return -1;
}
private void shiftDown(Node[] pointers, int amount) {
Node[] newPointers = new Node[this.m + 1];
for (int i = amount; i < pointers.length; i++) {
newPointers[i - amount] = pointers[i];
}
pointers = newPointers;
}
private void sortDictionary(DictionaryPair[] dictionary) {
Arrays.sort(dictionary, new Comparator<DictionaryPair>() {
@Override
public int compare(DictionaryPair o1, DictionaryPair o2) {
if (o1 == null && o2 == null) {
return 0;
}
if (o1 == null) {
return 1;
}
if (o2 == null) {
return -1;
}
return o1.compareTo(o2);
}
});
}
private Node[] splitChildPointers(InternalNode in, int split) {
Node[] pointers = in.childPointers;
Node[] halfPointers = new Node[this.m + 1];
for (int i = split + 1; i < pointers.length; i++) {
halfPointers[i - split - 1] = pointers[i];
in.removePointer(i);
}
return halfPointers;
}
private DictionaryPair[] splitDictionary(LeafNode ln, int split) {
DictionaryPair[] dictionary = ln.dictionary;
DictionaryPair[] halfDict = new DictionaryPair[this.m];
for (int i = split; i < dictionary.length; i++) {
halfDict[i - split] = dictionary[i];
ln.delete(i);
}
return halfDict;
}
private void splitInternalNode(InternalNode in) {
InternalNode parent = in.parent;
int midpoint = getMidpoint();
int newParentKey = in.keys[midpoint];
Integer[] halfKeys = splitKeys(in.keys, midpoint);
Node[] halfPointers = splitChildPointers(in, midpoint);
in.degree = linearNullSearch(in.childPointers);
InternalNode sibling = new InternalNode(this.m, halfKeys, halfPointers);
for (Node pointer : halfPointers) {
if (pointer != null) {
pointer.parent = sibling;
}
}
sibling.rightSibling = in.rightSibling;
if (sibling.rightSibling != null) {
sibling.rightSibling.leftSibling = sibling;
}
in.rightSibling = sibling;
sibling.leftSibling = in;
if (parent == null) {
Integer[] keys = new Integer[this.m];
keys[0] = newParentKey;
InternalNode newRoot = new InternalNode(this.m, keys);
newRoot.appendChildPointer(in);
newRoot.appendChildPointer(sibling);
this.root = newRoot;
in.parent = newRoot;
sibling.parent = newRoot;
} else {
parent.keys[parent.degree - 1] = newParentKey;
Arrays.sort(parent.keys, 0, parent.degree);
int pointerIndex = parent.findIndexOfPointer(in) + 1;
parent.insertChildPointer(sibling, pointerIndex);
sibling.parent = parent;
}
}
private Integer[] splitKeys(Integer[] keys, int split) {
Integer[] halfKeys = new Integer[this.m];
keys[split] = null;
for (int i = split + 1; i < keys.length; i++) {
halfKeys[i - split - 1] = keys[i];
keys[i] = null;
}
return halfKeys;
}
public void insert(int key, double value) {
if (isEmpty()) {
LeafNode ln = new LeafNode(this.m, new DictionaryPair(key, value));
this.firstLeaf = ln;
} else {
LeafNode ln = (this.root == null) ? this.firstLeaf : findLeafNode(key);
if (!ln.insert(new DictionaryPair(key, value))) {
ln.dictionary[ln.numPairs] = new DictionaryPair(key, value);
ln.numPairs++;
sortDictionary(ln.dictionary);
int midpoint = getMidpoint();
DictionaryPair[] halfDict = splitDictionary(ln, midpoint);
if (ln.parent == null) {
Integer[] parent_keys = new Integer[this.m];
parent_keys[0] = halfDict[0].key;
InternalNode parent = new InternalNode(this.m, parent_keys);
ln.parent = parent;
parent.appendChildPointer(ln);
} else {
int newParentKey = halfDict[0].key;
ln.parent.keys[ln.parent.degree - 1] = newParentKey;
Arrays.sort(ln.parent.keys, 0, ln.parent.degree);
}
LeafNode newLeafNode = new LeafNode(this.m, halfDict, ln.parent);
int pointerIndex = ln.parent.findIndexOfPointer(ln) + 1;
ln.parent.insertChildPointer(newLeafNode, pointerIndex);
newLeafNode.rightSibling = ln.rightSibling;
if (newLeafNode.rightSibling != null) {
newLeafNode.rightSibling.leftSibling = newLeafNode;
}
ln.rightSibling = newLeafNode;
newLeafNode.leftSibling = ln;
if (this.root == null) {
this.root = ln.parent;
} else {
InternalNode in = ln.parent;
while (in != null) {
if (in.isOverfull()) {
splitInternalNode(in);
} else {
break;
}
in = in.parent;
}
}
}
}
}
public Double search(int key) {
if (isEmpty()) {
return null;
}
LeafNode ln = (this.root == null) ? this.firstLeaf : findLeafNode(key);
DictionaryPair[] dps = ln.dictionary;
int index = binarySearch(dps, ln.numPairs, key);
if (index < 0) {
return null;
} else {
return dps[index].value;
}
}
public ArrayList<Double> search(int lowerBound, int upperBound) {
ArrayList<Double> values = new ArrayList<Double>();
LeafNode currNode = this.firstLeaf;
while (currNode != null) {
DictionaryPair dps[] = currNode.dictionary;
for (DictionaryPair dp : dps) {
if (dp == null) {
break;
}
if (lowerBound <= dp.key && dp.key <= upperBound) {
values.add(dp.value);
}
}
currNode = currNode.rightSibling;
}
return values;
}
public BPlusTree(int m) {
this.m = m;
this.root = null;
}
public class Node {
InternalNode parent;
}
private class InternalNode extends Node {
int maxDegree;
int minDegree;
int degree;
InternalNode leftSibling;
InternalNode rightSibling;
Integer[] keys;
Node[] childPointers;
private void appendChildPointer(Node pointer) {
this.childPointers[degree] = pointer;
this.degree++;
}
private int findIndexOfPointer(Node pointer) {
for (int i = 0; i < childPointers.length; i++) {
if (childPointers[i] == pointer) {
return i;
}
}
return -1;
}
private void insertChildPointer(Node pointer, int index) {
for (int i = degree - 1; i >= index; i--) {
childPointers[i + 1] = childPointers[i];
}
this.childPointers[index] = pointer;
this.degree++;
}
private boolean isDeficient() {
return this.degree < this.minDegree;
}
private boolean isLendable() {
return this.degree > this.minDegree;
}
private boolean isMergeable() {
return this.degree == this.minDegree;
}
private boolean isOverfull() {
return this.degree == maxDegree + 1;
}
private void prependChildPointer(Node pointer) {
for (int i = degree - 1; i >= 0; i--) {
childPointers[i + 1] = childPointers[i];
}
this.childPointers[0] = pointer;
this.degree++;
}
private void removeKey(int index) {
this.keys[index] = null;
}
private void removePointer(int index) {
this.childPointers[index] = null;
this.degree--;
}
private void removePointer(Node pointer) {
for (int i = 0; i < childPointers.length; i++) {
if (childPointers[i] == pointer) {
this.childPointers[i] = null;
}
}
this.degree--;
}
private InternalNode(int m, Integer[] keys) {
this.maxDegree = m;
this.minDegree = (int) Math.ceil(m / 2.0);
this.degree = 0;
this.keys = keys;
this.childPointers = new Node[this.maxDegree + 1];
}
private InternalNode(int m, Integer[] keys, Node[] pointers) {
this.maxDegree = m;
this.minDegree = (int) Math.ceil(m / 2.0);
this.degree = linearNullSearch(pointers);
this.keys = keys;
this.childPointers = pointers;
}
}
public class LeafNode extends Node {
int maxNumPairs;
int minNumPairs;
int numPairs;
LeafNode leftSibling;
LeafNode rightSibling;
DictionaryPair[] dictionary;
public void delete(int index) {
this.dictionary[index] = null;
numPairs--;
}
public boolean insert(DictionaryPair dp) {
if (this.isFull()) {
return false;
} else {
this.dictionary[numPairs] = dp;
numPairs++;
Arrays.sort(this.dictionary, 0, numPairs);
return true;
}
}
public boolean isDeficient() {
return numPairs < minNumPairs;
}
public boolean isFull() {
return numPairs == maxNumPairs;
}
public boolean isLendable() {
return numPairs > minNumPairs;
}
public boolean isMergeable() {
return numPairs == minNumPairs;
}
public LeafNode(int m, DictionaryPair dp) {
this.maxNumPairs = m - 1;
this.minNumPairs = (int) (Math.ceil(m / 2) - 1);
this.dictionary = new DictionaryPair[m];
this.numPairs = 0;
this.insert(dp);
}
public LeafNode(int m, DictionaryPair[] dps, InternalNode parent) {
this.maxNumPairs = m - 1;
this.minNumPairs = (int) (Math.ceil(m / 2) - 1);
this.dictionary = dps;
this.numPairs = linearNullSearch(dps);
this.parent = parent;
}
}
public class DictionaryPair implements Comparable<DictionaryPair> {
int key;
double value;
public DictionaryPair(int key, double value) {
this.key = key;
this.value = value;
}
public int compareTo(DictionaryPair o) {
if (key == o.key) {
return 0;
} else if (key > o.key) {
return 1;
} else {
return -1;
}
}
}
public static void main(String[] args) {
BPlusTree bpt = null;
bpt = new BPlusTree(3);
bpt.insert(5, 33);
bpt.insert(15, 21);
bpt.insert(25, 31);
bpt.insert(35, 41);
bpt.insert(45, 10);
if (bpt.search(15) != null) {
System.out.println("Found");
} else {
System.out.println("Not Found");
}
;
}
}