什么是B+树

B+ 树是自平衡树的高级形式,其中所有值都位于叶子节点。 在学习B+树之前要理解的一个重要概念是多级索引。在多级索引中,索引的索引创建如下图。它使访问数据更容易、更快。

B 树 - 图1
使用 B+ 树的多级索引

B+树的性质

  • 所有的叶子都在同一水平线上。
  • 根至少有两个孩子。
  • 除根之外的每个节点最多可以有 m 个子节点和至少 m/2 个子节点。
  • 每个节点最多可以包含 m - 1 个键和至少 ⌈m/2⌉ - 1 个键。

B树和B+树的比较

数据指针仅存在于 B+树的叶节点中,而数据指针可以存在于B树的内部、叶或根节点中。叶子节点在B树上不相互连接,而它们在B+树上相互连接。B+树上的操作比 B树上的操作快。

B 树 - 图2
B树
B 树 - 图3
B+树

B+树的操作

B+树的搜索

搜索操作

按照以下步骤在B+序树中搜索的数据为k.

  1. 从根节点开始。将 k 与根节点的键进行比较 [k1, k2, k3,……km - 1].
  2. 如果 k < k1,转到根节点的左子节点。
  3. 否则如果 k == k1, 继续和k2比较. 如果k < k2, 则k 介于k1和 k2之间. 所以搜索k2的左子节点.
  4. 如果 k > k2, 继续与 k3, k4,…km -1 重复第 2 步和第 3 步。
  5. 重复以上步骤,直到到达叶子节点。
  6. 如果 k 存在于叶节点中,则返回 true 否则返回 false。

搜索示例

让我们在下面的 B+ 树上搜索 k = 45

B 树 - 图4
B+树
  1. 将 k 与根节点进行比较。 | B 树 - 图5 | | —- | | 在根处找不到 k |
  1. 由于 k > 25,去右孩子。 | B 树 - 图6 | | —- | | 转到根的右侧 |
  1. 将 k 与 35 进行比较。由于 k > 30,将 k 与 45 进行比较。 | B 树 - 图7 | | —- | | 没有找到 |
  1. 由于 k ≥ 45,所以继续搜索右孩子。 | B 树 - 图8 | | —- | | 向右走 |
  1. k 被发现。 | B 树 - 图9 | | —- | | 找到 k |

搜索时间复杂度

如果在节点内部实现线性搜索,则总复杂度为 Θ(logt n).
如果使用二分查找,则总复杂度为 Θ(log2t.logt n).

B+树的插入

将元素插入B+ 树包括三个主要事件:搜索适当的叶子插入元素平衡/分裂树

插入约束

插入操作在将元素插入 B+ 树之前,必须牢记这些约束:

  • 根至少有两个孩子。
  • 除根之外的每个节点最多可以有 m 个孩子和至少 m/2 的孩子。
  • 每个节点最多可以包含 m - 1 个键和至少 ⌈m/2⌉ - 1 键。

插入算法

按照以下步骤插入元素。

  1. 由于每个元素都插入到叶节点中,因此请转到相应的叶节点。
  2. 将key插入叶节点。

案例一:叶子节点未满

  1. 如果叶子节点未满,则将key按升序插入叶子节点。

案例二:叶子节点已满

  1. 如果叶子节点已满,则将key按升序插入叶子节点,并按以下方式平衡树。
  2. 在 m/2 位置断开节点。
  3. 将m/2位置的key添加到父节点。
  4. 如果父节点已满,请执行步骤 2 至 3。

插入示例

要插入的元素是 5,15, 25, 35, 45。

  1. 插入 5 | B 树 - 图10 | | —- | | 插入 5 |

  2. 插入 15 | B 树 - 图11 | | —- | | 插入 15 |

  3. 插入 25。(在 15 位置分裂成2个节点。 将key 15添加到父节点。) | B 树 - 图12 | | —- | | 插入 25 |

  4. 插入 35(在 25位置分裂成2个节点。 将key 25添加到父节点。) | B 树 - 图13 | | —- | | 插入 35 |

  5. 插入 45 | B 树 - 图14 | | —- | | 插入 45 |

插入复杂度

  • 时间复杂度:Θ(t.logt n)

B+树的删除

删除 B+ 树上的元素包括三个主要事件:搜索要删除的键所在的节点,删除键并在需要时平衡树。下溢是一种情况,当节点中的键数少于它应该保存的最小键数时。

删除约束

在进行以下步骤之前,必须了解有关度数为m(例如m=3)的 B+树的这些事实。

  1. 一个节点最多可以有 m 个子节点。(即 3)
  2. 一个节点最多可以包含 m - 1 个键。(即 2)
  3. 一个节点应该有最少的 ⌈m/2⌉ 个子节点。(即 2)
  4. 一个节点(除了根节点)应该包含最少的⌈m/2⌉ - 1 个键。(即 1)

在删除键时,我们还必须注意内部节点(即索引)中存在的键,因为这些值在 B+ 树中是多余的。搜索要删除的key,然后按照以下步骤操作。

删除算法

案例一:删除的键仅在叶子节点

要删除的键仅存在于叶子节点中,而不存在于索引(或内部节点)中。它有两种情况:

  1. 节点中的键数超过最少key数量的约束,只需删除key。 | B 树 - 图15 | | —- | | 从B+树中删除 40 |
  1. 节点中的key数量低于最小约束。删除key并从直接兄弟中借用key将兄弟节点的中间键添加到父节点。 | B 树 - 图16 | | —- | | 从B+树中删除 5 |

案例二:删除的键也在内部节点

要删除的key即在叶子节点也存在于内部节点中。然后我们必须从内部节点中删除它们。这种情况有以下几种情况。

  1. 如果节点中的键数量高于最小数量的约束,只需从叶子节点中删除键并从内部节点中删除键即可用中序后继填充内部节点中的空白空间。 | B 树 - 图17 | | —- | | 从B+树中删除 45 |
  1. 如果节点中的键数低于最小数量的约束,则删除该键并从其直接兄弟(通过父级)借用一个键。用借用的键填充索引(内部节点)中创建的空白空间。 | B 树 - 图18 | | —- | | 从B+树中删除 35 |
  1. 这种情况类似于案例二(1),但在这里,在直接父节点上方生成空白空间。删除键后,将空白空间(不一定为空)与其兄弟合并。用中序后继节点填充祖父节点中的空白空间。 | B 树 - 图19 | | —- | | 从B+树中删除 25 |

案例三:删除节点导致平衡打破

在这种情况下,树的高度会缩小。有点复杂。从下面的树中删除 55 会导致这种情况。可以在下面的插图中理解。

B 树 - 图20
从 B 树中删除 55

B+树的应用

  • 多级索引
  • 树上更快的操作(插入、删除、搜索)
  • 数据库索引

B+树的实现

  1. // Searching on a B+ tree in Java
  2. import java.util.*;
  3. public class BPlusTree {
  4. int m;
  5. InternalNode root;
  6. LeafNode firstLeaf;
  7. // Binary search program
  8. private int binarySearch(DictionaryPair[] dps, int numPairs, int t) {
  9. Comparator<DictionaryPair> c = new Comparator<DictionaryPair>() {
  10. @Override
  11. public int compare(DictionaryPair o1, DictionaryPair o2) {
  12. Integer a = Integer.valueOf(o1.key);
  13. Integer b = Integer.valueOf(o2.key);
  14. return a.compareTo(b);
  15. }
  16. };
  17. return Arrays.binarySearch(dps, 0, numPairs, new DictionaryPair(t, 0), c);
  18. }
  19. // Find the leaf node
  20. private LeafNode findLeafNode(int key) {
  21. Integer[] keys = this.root.keys;
  22. int i;
  23. for (i = 0; i < this.root.degree - 1; i++) {
  24. if (key < keys[i]) {
  25. break;
  26. }
  27. }
  28. Node child = this.root.childPointers[i];
  29. if (child instanceof LeafNode) {
  30. return (LeafNode) child;
  31. } else {
  32. return findLeafNode((InternalNode) child, key);
  33. }
  34. }
  35. // Find the leaf node
  36. private LeafNode findLeafNode(InternalNode node, int key) {
  37. Integer[] keys = node.keys;
  38. int i;
  39. for (i = 0; i < node.degree - 1; i++) {
  40. if (key < keys[i]) {
  41. break;
  42. }
  43. }
  44. Node childNode = node.childPointers[i];
  45. if (childNode instanceof LeafNode) {
  46. return (LeafNode) childNode;
  47. } else {
  48. return findLeafNode((InternalNode) node.childPointers[i], key);
  49. }
  50. }
  51. // Finding the index of the pointer
  52. private int findIndexOfPointer(Node[] pointers, LeafNode node) {
  53. int i;
  54. for (i = 0; i < pointers.length; i++) {
  55. if (pointers[i] == node) {
  56. break;
  57. }
  58. }
  59. return i;
  60. }
  61. // Get the mid point
  62. private int getMidpoint() {
  63. return (int) Math.ceil((this.m + 1) / 2.0) - 1;
  64. }
  65. // Balance the tree
  66. private void handleDeficiency(InternalNode in) {
  67. InternalNode sibling;
  68. InternalNode parent = in.parent;
  69. if (this.root == in) {
  70. for (int i = 0; i < in.childPointers.length; i++) {
  71. if (in.childPointers[i] != null) {
  72. if (in.childPointers[i] instanceof InternalNode) {
  73. this.root = (InternalNode) in.childPointers[i];
  74. this.root.parent = null;
  75. } else if (in.childPointers[i] instanceof LeafNode) {
  76. this.root = null;
  77. }
  78. }
  79. }
  80. }
  81. else if (in.leftSibling != null && in.leftSibling.isLendable()) {
  82. sibling = in.leftSibling;
  83. } else if (in.rightSibling != null && in.rightSibling.isLendable()) {
  84. sibling = in.rightSibling;
  85. int borrowedKey = sibling.keys[0];
  86. Node pointer = sibling.childPointers[0];
  87. in.keys[in.degree - 1] = parent.keys[0];
  88. in.childPointers[in.degree] = pointer;
  89. parent.keys[0] = borrowedKey;
  90. sibling.removePointer(0);
  91. Arrays.sort(sibling.keys);
  92. sibling.removePointer(0);
  93. shiftDown(in.childPointers, 1);
  94. } else if (in.leftSibling != null && in.leftSibling.isMergeable()) {
  95. } else if (in.rightSibling != null && in.rightSibling.isMergeable()) {
  96. sibling = in.rightSibling;
  97. sibling.keys[sibling.degree - 1] = parent.keys[parent.degree - 2];
  98. Arrays.sort(sibling.keys, 0, sibling.degree);
  99. parent.keys[parent.degree - 2] = null;
  100. for (int i = 0; i < in.childPointers.length; i++) {
  101. if (in.childPointers[i] != null) {
  102. sibling.prependChildPointer(in.childPointers[i]);
  103. in.childPointers[i].parent = sibling;
  104. in.removePointer(i);
  105. }
  106. }
  107. parent.removePointer(in);
  108. sibling.leftSibling = in.leftSibling;
  109. }
  110. if (parent != null && parent.isDeficient()) {
  111. handleDeficiency(parent);
  112. }
  113. }
  114. private boolean isEmpty() {
  115. return firstLeaf == null;
  116. }
  117. private int linearNullSearch(DictionaryPair[] dps) {
  118. for (int i = 0; i < dps.length; i++) {
  119. if (dps[i] == null) {
  120. return i;
  121. }
  122. }
  123. return -1;
  124. }
  125. private int linearNullSearch(Node[] pointers) {
  126. for (int i = 0; i < pointers.length; i++) {
  127. if (pointers[i] == null) {
  128. return i;
  129. }
  130. }
  131. return -1;
  132. }
  133. private void shiftDown(Node[] pointers, int amount) {
  134. Node[] newPointers = new Node[this.m + 1];
  135. for (int i = amount; i < pointers.length; i++) {
  136. newPointers[i - amount] = pointers[i];
  137. }
  138. pointers = newPointers;
  139. }
  140. private void sortDictionary(DictionaryPair[] dictionary) {
  141. Arrays.sort(dictionary, new Comparator<DictionaryPair>() {
  142. @Override
  143. public int compare(DictionaryPair o1, DictionaryPair o2) {
  144. if (o1 == null && o2 == null) {
  145. return 0;
  146. }
  147. if (o1 == null) {
  148. return 1;
  149. }
  150. if (o2 == null) {
  151. return -1;
  152. }
  153. return o1.compareTo(o2);
  154. }
  155. });
  156. }
  157. private Node[] splitChildPointers(InternalNode in, int split) {
  158. Node[] pointers = in.childPointers;
  159. Node[] halfPointers = new Node[this.m + 1];
  160. for (int i = split + 1; i < pointers.length; i++) {
  161. halfPointers[i - split - 1] = pointers[i];
  162. in.removePointer(i);
  163. }
  164. return halfPointers;
  165. }
  166. private DictionaryPair[] splitDictionary(LeafNode ln, int split) {
  167. DictionaryPair[] dictionary = ln.dictionary;
  168. DictionaryPair[] halfDict = new DictionaryPair[this.m];
  169. for (int i = split; i < dictionary.length; i++) {
  170. halfDict[i - split] = dictionary[i];
  171. ln.delete(i);
  172. }
  173. return halfDict;
  174. }
  175. private void splitInternalNode(InternalNode in) {
  176. InternalNode parent = in.parent;
  177. int midpoint = getMidpoint();
  178. int newParentKey = in.keys[midpoint];
  179. Integer[] halfKeys = splitKeys(in.keys, midpoint);
  180. Node[] halfPointers = splitChildPointers(in, midpoint);
  181. in.degree = linearNullSearch(in.childPointers);
  182. InternalNode sibling = new InternalNode(this.m, halfKeys, halfPointers);
  183. for (Node pointer : halfPointers) {
  184. if (pointer != null) {
  185. pointer.parent = sibling;
  186. }
  187. }
  188. sibling.rightSibling = in.rightSibling;
  189. if (sibling.rightSibling != null) {
  190. sibling.rightSibling.leftSibling = sibling;
  191. }
  192. in.rightSibling = sibling;
  193. sibling.leftSibling = in;
  194. if (parent == null) {
  195. Integer[] keys = new Integer[this.m];
  196. keys[0] = newParentKey;
  197. InternalNode newRoot = new InternalNode(this.m, keys);
  198. newRoot.appendChildPointer(in);
  199. newRoot.appendChildPointer(sibling);
  200. this.root = newRoot;
  201. in.parent = newRoot;
  202. sibling.parent = newRoot;
  203. } else {
  204. parent.keys[parent.degree - 1] = newParentKey;
  205. Arrays.sort(parent.keys, 0, parent.degree);
  206. int pointerIndex = parent.findIndexOfPointer(in) + 1;
  207. parent.insertChildPointer(sibling, pointerIndex);
  208. sibling.parent = parent;
  209. }
  210. }
  211. private Integer[] splitKeys(Integer[] keys, int split) {
  212. Integer[] halfKeys = new Integer[this.m];
  213. keys[split] = null;
  214. for (int i = split + 1; i < keys.length; i++) {
  215. halfKeys[i - split - 1] = keys[i];
  216. keys[i] = null;
  217. }
  218. return halfKeys;
  219. }
  220. public void insert(int key, double value) {
  221. if (isEmpty()) {
  222. LeafNode ln = new LeafNode(this.m, new DictionaryPair(key, value));
  223. this.firstLeaf = ln;
  224. } else {
  225. LeafNode ln = (this.root == null) ? this.firstLeaf : findLeafNode(key);
  226. if (!ln.insert(new DictionaryPair(key, value))) {
  227. ln.dictionary[ln.numPairs] = new DictionaryPair(key, value);
  228. ln.numPairs++;
  229. sortDictionary(ln.dictionary);
  230. int midpoint = getMidpoint();
  231. DictionaryPair[] halfDict = splitDictionary(ln, midpoint);
  232. if (ln.parent == null) {
  233. Integer[] parent_keys = new Integer[this.m];
  234. parent_keys[0] = halfDict[0].key;
  235. InternalNode parent = new InternalNode(this.m, parent_keys);
  236. ln.parent = parent;
  237. parent.appendChildPointer(ln);
  238. } else {
  239. int newParentKey = halfDict[0].key;
  240. ln.parent.keys[ln.parent.degree - 1] = newParentKey;
  241. Arrays.sort(ln.parent.keys, 0, ln.parent.degree);
  242. }
  243. LeafNode newLeafNode = new LeafNode(this.m, halfDict, ln.parent);
  244. int pointerIndex = ln.parent.findIndexOfPointer(ln) + 1;
  245. ln.parent.insertChildPointer(newLeafNode, pointerIndex);
  246. newLeafNode.rightSibling = ln.rightSibling;
  247. if (newLeafNode.rightSibling != null) {
  248. newLeafNode.rightSibling.leftSibling = newLeafNode;
  249. }
  250. ln.rightSibling = newLeafNode;
  251. newLeafNode.leftSibling = ln;
  252. if (this.root == null) {
  253. this.root = ln.parent;
  254. } else {
  255. InternalNode in = ln.parent;
  256. while (in != null) {
  257. if (in.isOverfull()) {
  258. splitInternalNode(in);
  259. } else {
  260. break;
  261. }
  262. in = in.parent;
  263. }
  264. }
  265. }
  266. }
  267. }
  268. public Double search(int key) {
  269. if (isEmpty()) {
  270. return null;
  271. }
  272. LeafNode ln = (this.root == null) ? this.firstLeaf : findLeafNode(key);
  273. DictionaryPair[] dps = ln.dictionary;
  274. int index = binarySearch(dps, ln.numPairs, key);
  275. if (index < 0) {
  276. return null;
  277. } else {
  278. return dps[index].value;
  279. }
  280. }
  281. public ArrayList<Double> search(int lowerBound, int upperBound) {
  282. ArrayList<Double> values = new ArrayList<Double>();
  283. LeafNode currNode = this.firstLeaf;
  284. while (currNode != null) {
  285. DictionaryPair dps[] = currNode.dictionary;
  286. for (DictionaryPair dp : dps) {
  287. if (dp == null) {
  288. break;
  289. }
  290. if (lowerBound <= dp.key && dp.key <= upperBound) {
  291. values.add(dp.value);
  292. }
  293. }
  294. currNode = currNode.rightSibling;
  295. }
  296. return values;
  297. }
  298. public BPlusTree(int m) {
  299. this.m = m;
  300. this.root = null;
  301. }
  302. public class Node {
  303. InternalNode parent;
  304. }
  305. private class InternalNode extends Node {
  306. int maxDegree;
  307. int minDegree;
  308. int degree;
  309. InternalNode leftSibling;
  310. InternalNode rightSibling;
  311. Integer[] keys;
  312. Node[] childPointers;
  313. private void appendChildPointer(Node pointer) {
  314. this.childPointers[degree] = pointer;
  315. this.degree++;
  316. }
  317. private int findIndexOfPointer(Node pointer) {
  318. for (int i = 0; i < childPointers.length; i++) {
  319. if (childPointers[i] == pointer) {
  320. return i;
  321. }
  322. }
  323. return -1;
  324. }
  325. private void insertChildPointer(Node pointer, int index) {
  326. for (int i = degree - 1; i >= index; i--) {
  327. childPointers[i + 1] = childPointers[i];
  328. }
  329. this.childPointers[index] = pointer;
  330. this.degree++;
  331. }
  332. private boolean isDeficient() {
  333. return this.degree < this.minDegree;
  334. }
  335. private boolean isLendable() {
  336. return this.degree > this.minDegree;
  337. }
  338. private boolean isMergeable() {
  339. return this.degree == this.minDegree;
  340. }
  341. private boolean isOverfull() {
  342. return this.degree == maxDegree + 1;
  343. }
  344. private void prependChildPointer(Node pointer) {
  345. for (int i = degree - 1; i >= 0; i--) {
  346. childPointers[i + 1] = childPointers[i];
  347. }
  348. this.childPointers[0] = pointer;
  349. this.degree++;
  350. }
  351. private void removeKey(int index) {
  352. this.keys[index] = null;
  353. }
  354. private void removePointer(int index) {
  355. this.childPointers[index] = null;
  356. this.degree--;
  357. }
  358. private void removePointer(Node pointer) {
  359. for (int i = 0; i < childPointers.length; i++) {
  360. if (childPointers[i] == pointer) {
  361. this.childPointers[i] = null;
  362. }
  363. }
  364. this.degree--;
  365. }
  366. private InternalNode(int m, Integer[] keys) {
  367. this.maxDegree = m;
  368. this.minDegree = (int) Math.ceil(m / 2.0);
  369. this.degree = 0;
  370. this.keys = keys;
  371. this.childPointers = new Node[this.maxDegree + 1];
  372. }
  373. private InternalNode(int m, Integer[] keys, Node[] pointers) {
  374. this.maxDegree = m;
  375. this.minDegree = (int) Math.ceil(m / 2.0);
  376. this.degree = linearNullSearch(pointers);
  377. this.keys = keys;
  378. this.childPointers = pointers;
  379. }
  380. }
  381. public class LeafNode extends Node {
  382. int maxNumPairs;
  383. int minNumPairs;
  384. int numPairs;
  385. LeafNode leftSibling;
  386. LeafNode rightSibling;
  387. DictionaryPair[] dictionary;
  388. public void delete(int index) {
  389. this.dictionary[index] = null;
  390. numPairs--;
  391. }
  392. public boolean insert(DictionaryPair dp) {
  393. if (this.isFull()) {
  394. return false;
  395. } else {
  396. this.dictionary[numPairs] = dp;
  397. numPairs++;
  398. Arrays.sort(this.dictionary, 0, numPairs);
  399. return true;
  400. }
  401. }
  402. public boolean isDeficient() {
  403. return numPairs < minNumPairs;
  404. }
  405. public boolean isFull() {
  406. return numPairs == maxNumPairs;
  407. }
  408. public boolean isLendable() {
  409. return numPairs > minNumPairs;
  410. }
  411. public boolean isMergeable() {
  412. return numPairs == minNumPairs;
  413. }
  414. public LeafNode(int m, DictionaryPair dp) {
  415. this.maxNumPairs = m - 1;
  416. this.minNumPairs = (int) (Math.ceil(m / 2) - 1);
  417. this.dictionary = new DictionaryPair[m];
  418. this.numPairs = 0;
  419. this.insert(dp);
  420. }
  421. public LeafNode(int m, DictionaryPair[] dps, InternalNode parent) {
  422. this.maxNumPairs = m - 1;
  423. this.minNumPairs = (int) (Math.ceil(m / 2) - 1);
  424. this.dictionary = dps;
  425. this.numPairs = linearNullSearch(dps);
  426. this.parent = parent;
  427. }
  428. }
  429. public class DictionaryPair implements Comparable<DictionaryPair> {
  430. int key;
  431. double value;
  432. public DictionaryPair(int key, double value) {
  433. this.key = key;
  434. this.value = value;
  435. }
  436. public int compareTo(DictionaryPair o) {
  437. if (key == o.key) {
  438. return 0;
  439. } else if (key > o.key) {
  440. return 1;
  441. } else {
  442. return -1;
  443. }
  444. }
  445. }
  446. public static void main(String[] args) {
  447. BPlusTree bpt = null;
  448. bpt = new BPlusTree(3);
  449. bpt.insert(5, 33);
  450. bpt.insert(15, 21);
  451. bpt.insert(25, 31);
  452. bpt.insert(35, 41);
  453. bpt.insert(45, 10);
  454. if (bpt.search(15) != null) {
  455. System.out.println("Found");
  456. } else {
  457. System.out.println("Not Found");
  458. }
  459. ;
  460. }
  461. }