当前位置:   article > 正文

平衡树:AVL 树、红黑树、B 树、B+ 树

平衡树:AVL 树、红黑树、B 树、B+ 树

平衡树

平衡树是一种数据结构,它能够保持在插入或删除操作后,树的高度保持较小的增量,从而保持树的平衡性,提高了查找、插入和删除操作的效率。AVL树、红黑树、B树以及B+树都是常见的平衡树的变种,它们在不同的应用场景中具有不同的优势和特点。

  • AVL 树在需要严格平衡的场景下使用,例如需要快速的插入、删除和查找操作,而对内存消耗要求较高的情况。

  • 红黑树是一种近似平衡的二叉搜索树,它在插入和删除操作上比 AVL 树更加高效,常用于各种编程语言的标准库中,例如 C++ 的 std::map 和 Java 的 TreeMap

  • B 树和 B+ 树广泛应用于数据库索引和文件系统中,它们能够有效地支持范围查询和顺序访问,并且能够处理大量数据。

​​​​​​​

AVL树(严格平衡的二叉搜索树)

AVL树是一种严格平衡的二叉搜索树,它要求任何节点的左子树和右子树的高度最多相差1。为了维持平衡,AVL树在插入或删除操作后会通过旋转操作来调整树的结构,以保持平衡性。AVL树的查询、插入和删除操作的时间复杂度都是O(log n)。

代码示例:

  1. class AVLNode {
  2. /**
  3. * 构造函数,用于创建一棵二叉树节点
  4. *
  5. * @param key 节点的键值
  6. */
  7. constructor(key) {
  8. this.key = key;
  9. this.left = null;
  10. this.right = null;
  11. this.height = 1;
  12. }
  13. }
  14. class AVLTree {
  15. /**
  16. * 构造函数
  17. *
  18. * @returns 无返回值
  19. */
  20. constructor() {
  21. this.root = null;
  22. }
  23. /**
  24. * 获取节点高度
  25. *
  26. * @param node 节点对象
  27. * @returns 返回节点高度,如果节点不存在则返回0
  28. */
  29. getHeight(node) {
  30. // 如果节点存在,则返回节点的高度,否则返回0
  31. return node ? node.height : 0;
  32. }
  33. /**
  34. * 获取节点的平衡因子
  35. *
  36. * @param node 节点对象
  37. * @returns 返回节点的平衡因子,若节点为空则返回0
  38. */
  39. getBalanceFactor(node) {
  40. // 如果节点存在,则返回节点的高度,否则返回0
  41. return node ? this.getHeight(node.left) - this.getHeight(node.right) : 0;
  42. }
  43. /**
  44. * 更新节点高度
  45. *
  46. * @param node 节点对象
  47. */
  48. updateHeight(node) {
  49. // 更新节点的高度为左子树和右子树中较高的高度加1
  50. node.height = Math.max(this.getHeight(node.left), this.getHeight(node.right)) + 1;
  51. }
  52. /**
  53. * 将节点y进行右旋操作
  54. *
  55. * @param y 要进行右旋操作的节点
  56. * @returns 返回旋转后的新根节点
  57. */
  58. rotateRight(y) {
  59. // 将y的左子节点赋值给x
  60. let x = y.left;
  61. // 将x的右子节点赋值给T2
  62. let T2 = x.right;
  63. // 将y赋值给x的右子节点
  64. x.right = y;
  65. // 将T2赋值给y的左子节点
  66. y.left = T2;
  67. // 更新y的高度
  68. this.updateHeight(y);
  69. // 更新x的高度
  70. this.updateHeight(x);
  71. // 返回x
  72. return x;
  73. }
  74. /**
  75. * 左旋操作
  76. *
  77. * @param x 待旋转的节点
  78. * @returns 返回旋转后的新根节点
  79. */
  80. rotateLeft(x) {
  81. // 将节点x的右子节点赋值给y
  82. let y = x.right;
  83. // 将节点y的左子节点赋值给T2
  84. let T2 = y.left;
  85. // 将节点x赋值给节点y的左子节点
  86. y.left = x;
  87. // 将T2赋值给节点x的右子节点
  88. x.right = T2;
  89. // 更新节点x的高度
  90. this.updateHeight(x);
  91. // 更新节点y的高度
  92. this.updateHeight(y);
  93. // 返回旋转后的新根节点y
  94. return y;
  95. }
  96. /**
  97. * 向AVL树中插入节点
  98. *
  99. * @param node 当前节点
  100. * @param key 待插入节点的键值
  101. * @returns 返回插入后的节点
  102. */
  103. insert(node, key) {
  104. // 如果节点为空,则创建一个新的节点并返回
  105. if (!node) {
  106. return new AVLNode(key);
  107. }
  108. // 如果插入的键小于节点的键,则在左子树中插入
  109. if (key < node.key) {
  110. node.left = this.insert(node.left, key);
  111. }
  112. // 如果插入的键大于节点的键,则在右子树中插入
  113. else if (key > node.key) {
  114. node.right = this.insert(node.right, key);
  115. }
  116. // 如果插入的键等于节点的键,则直接返回该节点
  117. else {
  118. return node;
  119. }
  120. // 更新节点的高度
  121. this.updateHeight(node);
  122. // 获取节点的平衡因子
  123. let balance = this.getBalanceFactor(node);
  124. // 左左情况:左子树高度大于右子树高度2及以上,且插入的键小于左子树的最小键
  125. // 右旋
  126. // 左左情况
  127. if (balance > 1 && key < node.left.key) {
  128. return this.rotateRight(node);
  129. }
  130. // 右右情况:右子树高度大于左子树高度2及以上,且插入的键大于右子树的最大键
  131. // 左旋
  132. // 右右情况
  133. if (balance < -1 && key > node.right.key) {
  134. return this.rotateLeft(node);
  135. }
  136. // 左右情况:左子树高度大于右子树高度2及以上,且插入的键大于左子树的最小键
  137. // 先左旋,再右旋
  138. // 左右情况
  139. if (balance > 1 && key > node.left.key) {
  140. node.left = this.rotateLeft(node.left);
  141. return this.rotateRight(node);
  142. }
  143. // 右左情况:右子树高度大于左子树高度2及以上,且插入的键小于右子树的最大键
  144. // 先右旋,再左旋
  145. // 右左情况
  146. if (balance < -1 && key < node.right.key) {
  147. node.right = this.rotateRight(node.right);
  148. return this.rotateLeft(node);
  149. }
  150. // 返回插入后的节点
  151. return node;
  152. }
  153. }
  154. // 示例
  155. const avlTree = new AVLTree();
  156. avlTree.root = avlTree.insert(avlTree.root, 10);
  157. avlTree.root = avlTree.insert(avlTree.root, 20);
  158. avlTree.root = avlTree.insert(avlTree.root, 30);
  159. console.log(avlTree.root); // AVLNode { key: 20, left: AVLNode { key: 10, ... }, right: AVLNode { key: 30, ... }, ... }

红黑树

红黑树是一种近似平衡的二叉搜索树,它通过一些附加的约束条件来保持树的大致平衡。红黑树的节点可以是红色或黑色,它要求在插入或删除操作后,树的黑色高度相同。红黑树通过颜色变换和旋转操作来调整树的结构,以满足约束条件。红黑树相对于AVL树来说,旋转操作更少,插入和删除操作更快,但是查询操作略慢一些。

代码示例:

  1. const RED = 'red';
  2. const BLACK = 'black';
  3. class RedBlackNode {
  4. /**
  5. * 构造函数,用于创建一个节点对象
  6. *
  7. * @param key 节点的键值
  8. * @param color 节点的颜色
  9. */
  10. constructor(key, color) {
  11. this.key = key; // 设置当前节点的键值
  12. this.left = null; // 初始化左子节点为 null
  13. this.right = null;// 初始化右子节点为 null
  14. this.parent = null; // 初始化父节点为 null
  15. this.color = color;// 设置当前节点的颜色
  16. }
  17. }
  18. class RedBlackTree {
  19. /**
  20. * 构造函数
  21. */
  22. constructor() {
  23. // 初始化根节点为null
  24. this.root = null;
  25. }
  26. /**
  27. * 将二叉树节点左旋转
  28. *
  29. * @param node 需要左旋转的节点
  30. */
  31. rotateLeft(node) {
  32. // 获取当前节点的右子节点
  33. let rightChild = node.right;
  34. // 将当前节点的右子节点的左子节点赋值给当前节点的右子节点
  35. node.right = rightChild.left;
  36. // 如果当前节点的右子节点的左子节点不为空
  37. if (rightChild.left !== null) {
  38. // 将当前节点的右子节点的左子节点的父节点指向当前节点
  39. rightChild.left.parent = node;
  40. }
  41. // 将当前节点的右子节点的父节点指向当前节点的父节点
  42. rightChild.parent = node.parent;
  43. // 如果当前节点的父节点为空
  44. if (node.parent === null) {
  45. // 将当前节点的右子节点设置为根节点
  46. this.root = rightChild;
  47. // 如果当前节点是其父节点的左子节点
  48. } else if (node === node.parent.left) {
  49. // 将当前节点的父节点的左子节点设置为当前节点的右子节点
  50. node.parent.left = rightChild;
  51. // 如果当前节点是其父节点的右子节点
  52. } else {
  53. // 将当前节点的父节点的右子节点设置为当前节点的右子节点
  54. node.parent.right = rightChild;
  55. }
  56. // 将当前节点的右子节点的左子节点设置为当前节点
  57. rightChild.left = node;
  58. // 将当前节点的父节点设置为当前节点的右子节点
  59. node.parent = rightChild;
  60. }
  61. /**
  62. * 将节点右旋
  63. *
  64. * @param node 要旋转的节点
  65. */
  66. rotateRight(node) {
  67. // 获取当前节点的左子节点
  68. let leftChild = node.left;
  69. // 将当前节点的左子节点指向左子节点的右子节点
  70. node.left = leftChild.right;
  71. // 如果左子节点的右子节点不为空
  72. if (leftChild.right !== null) {
  73. // 将左子节点的右子节点的父节点指向当前节点
  74. leftChild.right.parent = node;
  75. }
  76. // 将左子节点的父节点指向当前节点的父节点
  77. leftChild.parent = node.parent;
  78. // 如果当前节点的父节点为空
  79. if (node.parent === null) {
  80. // 将根节点指向左子节点
  81. this.root = leftChild;
  82. // 如果当前节点是父节点的右子节点
  83. } else if (node === node.parent.right) {
  84. // 将父节点的右子节点指向左子节点
  85. node.parent.right = leftChild;
  86. // 否则
  87. } else {
  88. // 将父节点的左子节点指向左子节点
  89. node.parent.left = leftChild;
  90. }
  91. // 将左子节点的右子节点指向当前节点
  92. leftChild.right = node;
  93. // 将当前节点的父节点指向左子节点
  94. node.parent = leftChild;
  95. }
  96. /**
  97. * 插入节点
  98. *
  99. * @param key 节点值
  100. */
  101. insert(key) {
  102. // 创建一个新的红黑树节点,颜色为红色
  103. let newNode = new RedBlackNode(key, RED);
  104. // 将新节点插入到红黑树中,并返回更新后的根节点
  105. this.root = this.insertNode(this.root, newNode);
  106. // 修复红黑树的性质
  107. this.fixTreeProperties(newNode);
  108. }
  109. /**
  110. * 在二叉搜索树中插入节点
  111. *
  112. * @param root 二叉搜索树的根节点
  113. * @param node 要插入的节点
  114. * @returns 返回插入节点后的二叉搜索树的根节点
  115. */
  116. insertNode(root, node) {
  117. // 如果根节点为空,直接返回新节点作为根节点
  118. if (root === null) {
  119. return node;
  120. }
  121. // 如果新节点的键值小于根节点的键值,则递归地将新节点插入到左子树中
  122. if (node.key < root.key) {
  123. root.left = this.insertNode(root.left, node);
  124. // 将新节点的父节点设置为当前根节点
  125. root.left.parent = root;
  126. // 如果新节点的键值大于根节点的键值,则递归地将新节点插入到右子树中
  127. } else if (node.key > root.key) {
  128. root.right = this.insertNode(root.right, node);
  129. // 将新节点的父节点设置为当前根节点
  130. root.right.parent = root;
  131. }
  132. // 返回根节点
  133. return root;
  134. }
  135. /**
  136. * 修复树属性
  137. *
  138. * @param node 当前节点
  139. */
  140. fixTreeProperties(node) {
  141. // 当当前节点不是根节点且其父节点颜色为红色时,执行循环
  142. while (node !== this.root && node.parent.color === RED) {
  143. // 获取当前节点的父节点和祖父节点
  144. let parentNode = node.parent;
  145. let grandParentNode = node.parent.parent;
  146. // 如果父节点是祖父节点的左子节点
  147. if (parentNode === grandParentNode.left) {
  148. // 获取叔叔节点
  149. let uncleNode = grandParentNode.right;
  150. // 如果叔叔节点不为空且颜色为红色
  151. if (uncleNode !== null && uncleNode.color === RED) {
  152. // 将祖父节点颜色设为红色
  153. grandParentNode.color = RED;
  154. // 将父节点颜色设为黑色
  155. parentNode.color = BLACK;
  156. // 将叔叔节点颜色设为黑色
  157. uncleNode.color = BLACK;
  158. // 将当前节点更新为祖父节点,继续下一轮循环
  159. node = grandParentNode;
  160. } else {
  161. // 如果当前节点是父节点的右子节点
  162. if (node === parentNode.right) {
  163. // 执行左旋操作
  164. this.rotateLeft(parentNode);
  165. // 将当前节点更新为父节点
  166. node = parentNode;
  167. // 更新父节点为当前节点的父节点
  168. parentNode = node.parent;
  169. }
  170. // 执行右旋操作
  171. this.rotateRight(grandParentNode);
  172. // 交换父节点和祖父节点的颜色
  173. [parentNode.color, grandParentNode.color] = [grandParentNode.color, parentNode.color];
  174. // 将当前节点更新为父节点,继续下一轮循环
  175. node = parentNode;
  176. }
  177. } else {
  178. // 如果父节点是祖父节点的右子节点
  179. // 获取叔叔节点
  180. let uncleNode = grandParentNode.left;
  181. // 如果叔叔节点不为空且颜色为红色
  182. if (uncleNode !== null && uncleNode.color === RED) {
  183. // 将祖父节点颜色设为红色
  184. grandParentNode.color = RED;
  185. // 将父节点颜色设为黑色
  186. parentNode.color = BLACK;
  187. // 将叔叔节点颜色设为黑色
  188. uncleNode.color = BLACK;
  189. // 将当前节点更新为祖父节点,继续下一轮循环
  190. node = grandParentNode;
  191. } else {
  192. // 如果当前节点是父节点的左子节点
  193. if (node === parentNode.left) {
  194. // 执行右旋操作
  195. this.rotateRight(parentNode);
  196. // 将当前节点更新为父节点
  197. node = parentNode;
  198. // 更新父节点为当前节点的父节点
  199. parentNode = node.parent;
  200. }
  201. // 执行左旋操作
  202. this.rotateLeft(grandParentNode);
  203. // 交换父节点和祖父节点的颜色
  204. [parentNode.color, grandParentNode.color] = [grandParentNode.color, parentNode.color];
  205. // 将当前节点更新为父节点,继续下一轮循环
  206. node = parentNode;
  207. }
  208. }
  209. }
  210. // 将根节点颜色设为黑色
  211. this.root.color = BLACK;
  212. }
  213. }
  214. // 示例
  215. const rbTree = new RedBlackTree();
  216. rbTree.insert(10);
  217. rbTree.insert(20);
  218. rbTree.insert(30);
  219. console.log(rbTree.root); // RedBlackNode { key: 20, color: 'black', ... }

B树

B树是一种多路搜索树,它的每个节点可以存储多个关键字,并且可以有多个子节点。B树的节点拥有较大的容量,使得每个节点的关键字数量相对较少,这样可以降低树的高度,提高查询效率。B树常用于文件系统和数据库中,用于索引大量数据的情况。

代码示例:

  1. class BTreeNode {
  2. /**
  3. * 构造函数
  4. *
  5. * @param leaf 是否为叶子节点,默认为false
  6. */
  7. constructor(leaf = false) {
  8. this.keys = [];
  9. this.children = [];
  10. this.leaf = leaf;
  11. }
  12. }
  13. class BTree {
  14. /**
  15. * 构造函数
  16. *
  17. * @param degree 树的度数
  18. */
  19. constructor(degree) {
  20. this.root = null;
  21. this.degree = degree;
  22. }
  23. /**
  24. * 搜索指定节点
  25. *
  26. * @param key 要搜索的节点值
  27. * @returns 返回与key值相等的节点
  28. */
  29. search(key) {
  30. // 调用 searchNode 方法,传入根节点和关键字进行搜索
  31. return this.searchNode(this.root, key);
  32. }
  33. /**
  34. * 在 B-tree 节点中查找给定的键值,并返回该键值所在的节点
  35. *
  36. * @param node B-tree 节点
  37. * @param key 要查找的键值
  38. * @returns 返回找到的节点,如果未找到则返回 null
  39. */
  40. searchNode(node, key) {
  41. let i = 0;
  42. // 遍历节点的 keys 数组,查找 key 所在的索引位置
  43. while (i < node.keys.length && key > node.keys[i]) {
  44. i++;
  45. }
  46. // 如果找到了 key,并且 key 等于当前索引位置的元素,则返回当前节点
  47. if (i < node.keys.length && key === node.keys[i]) {
  48. return node;
  49. // 如果当前节点是叶子节点,则返回 null
  50. } else if (node.leaf) {
  51. return null;
  52. // 否则,在子节点中继续搜索 key
  53. } else {
  54. return this.searchNode(node.children[i], key);
  55. }
  56. }
  57. /**
  58. * 向B树中插入一个键值
  59. *
  60. * @param key 要插入的键值
  61. */
  62. insert(key) {
  63. // 如果根节点不存在
  64. if (!this.root) {
  65. // 创建一个新的B树节点作为根节点,并且标记为叶子节点
  66. this.root = new BTreeNode(true);
  67. // 将传入的键值添加到根节点的键值数组中
  68. this.root.keys.push(key);
  69. } else {
  70. // 如果根节点的键值数组已满(达到2倍的度数减1)
  71. if (this.root.keys.length === (2 * this.degree) - 1) {
  72. // 创建一个新的B树节点作为新的根节点,并且标记为非叶子节点
  73. let newRoot = new BTreeNode(false);
  74. // 将原来的根节点添加到新根节点的子节点数组中
  75. newRoot.children.push(this.root);
  76. // 调用splitChild方法拆分子节点
  77. this.splitChild(newRoot, 0, this.root);
  78. // 更新根节点为新创建的节点
  79. this.root = newRoot;
  80. }
  81. // 在非满的节点中插入键值
  82. this.insertNonFull(this.root, key);
  83. }
  84. }
  85. /**
  86. * 在非满节点中插入键值对
  87. *
  88. * @param node 节点对象
  89. * @param key 待插入的键
  90. */
  91. insertNonFull(node, key) {
  92. // 获取节点中键的最后一个索引
  93. let i = node.keys.length - 1;
  94. // 如果节点是叶子节点
  95. if (node.leaf) {
  96. // 当索引大于等于0且key小于当前索引对应的键时,索引减1
  97. while (i >= 0 && key < node.keys[i]) {
  98. i--;
  99. }
  100. // 在索引i+1的位置插入key
  101. node.keys.splice(i + 1, 0, key);
  102. } else {
  103. // 当索引大于等于0且key小于当前索引对应的键时,索引减1
  104. while (i >= 0 && key < node.keys[i]) {
  105. i--;
  106. }
  107. // 索引加1
  108. i++;
  109. // 如果当前索引对应的子节点的键的数量等于(2 * this.degree) - 1
  110. if (node.children[i].keys.length === (2 * this.degree) - 1) {
  111. // 调用splitChild方法拆分子节点
  112. this.splitChild(node, i, node.children[i]);
  113. // 如果key大于当前索引对应的键
  114. if (key > node.keys[i]) {
  115. // 索引加1
  116. i++;
  117. }
  118. }
  119. // 递归调用insertNonFull方法,将key插入到当前索引对应的子节点中
  120. this.insertNonFull(node.children[i], key);
  121. }
  122. }
  123. /**
  124. * 将子节点拆分为两个节点,并将拆分后的新节点插入到父节点中
  125. *
  126. * @param parent 父节点
  127. * @param index 父节点中子节点的索引
  128. * @param child 要拆分的子节点
  129. */
  130. splitChild(parent, index, child) {
  131. // 创建一个新的B树节点,叶子节点状态与child相同
  132. let newChild = new BTreeNode(child.leaf);
  133. // 计算中间索引位置
  134. let midIndex = Math.floor(this.degree - 1);
  135. // 将child节点的keys数组从中间索引位置后的部分赋值给newChild的keys数组
  136. newChild.keys = child.keys.splice(midIndex + 1);
  137. // 如果child节点不是叶子节点
  138. if (!child.leaf) {
  139. // 将child节点的children数组从中间索引位置后的部分赋值给newChild的children数组
  140. newChild.children = child.children.splice(midIndex + 1);
  141. }
  142. // 将child节点的keys数组中间索引位置的元素插入到parent节点的keys数组的指定位置
  143. parent.keys.splice(index, 0, child.keys[midIndex]);
  144. // 将newChild节点插入到parent节点的children数组的指定位置
  145. parent.children.splice(index + 1, 0, newChild);
  146. }
  147. }
  148. // 示例
  149. const bTree = new BTree(2);
  150. bTree.insert(10);
  151. bTree.insert(20);
  152. bTree.insert(5);
  153. console.log(bTree.search(10)); // BTreeNode { keys: [ 10 ], children: [], leaf: true }
  154. console.log(bTree.search(15)); // null

B+树

B+树是B树的一种变体,它与B树的区别在于B+树的非叶子节点不存储关键字的值,只存储关键字和指向子节点的指针。所有的叶子节点按顺序连接起来,形成一个有序链表,方便范围查询。B+树适用于数据库索引等需要范围查询的场景。

代码示例:

  1. class BPlusTreeNode {
  2. /**
  3. * 构造函数
  4. */
  5. constructor() {
  6. this.keys = [];
  7. this.children = [];
  8. }
  9. }
  10. class BPlusTree {
  11. /**
  12. * 构造函数
  13. *
  14. * @param degree 树的度数
  15. */
  16. constructor(degree) {
  17. this.root = null;
  18. this.degree = degree;
  19. }
  20. /**
  21. * 搜索指定关键字
  22. *
  23. * @param key 关键字
  24. * @returns 返回搜索到的节点,若未找到则返回null
  25. */
  26. search(key) {
  27. // 调用 searchNode 方法进行搜索
  28. return this.searchNode(this.root, key);
  29. }
  30. /**
  31. * 在树中搜索节点
  32. *
  33. * @param node 当前节点
  34. * @param key 待搜索的键
  35. * @returns 返回找到的节点,若未找到则返回null
  36. */
  37. searchNode(node, key) {
  38. // 如果节点为空,则返回null
  39. if (!node) return null;
  40. // 初始化索引i为0
  41. let i = 0;
  42. // 循环遍历节点的keys数组,直到找到key应该插入的位置
  43. while (i < node.keys.length && key >= node.keys[i]) {
  44. i++;
  45. }
  46. // 如果节点没有子节点,则返回当前节点
  47. if (node.children.length === 0) {
  48. return node;
  49. }
  50. // 递归调用searchNode方法,在子节点中继续搜索
  51. return this.searchNode(node.children[i], key);
  52. }
  53. /**
  54. * 向B+树中插入键值
  55. *
  56. * @param key 键值
  57. */
  58. insert(key) {
  59. // 如果根节点不存在
  60. if (!this.root) {
  61. // 创建一个新的根节点
  62. this.root = new BPlusTreeNode();
  63. // 将键值添加到根节点的键值数组中
  64. this.root.keys.push(key);
  65. } else {
  66. // 如果根节点的键值数组已满
  67. if (this.root.keys.length === (2 * this.degree) - 1) {
  68. // 创建一个新的根节点
  69. let newRoot = new BPlusTreeNode();
  70. // 将原根节点添加到新根节点的子节点数组中
  71. newRoot.children.push(this.root);
  72. // 调用 splitChild 方法进行节点分裂
  73. this.splitChild(newRoot, 0, this.root);
  74. // 更新根节点为新创建的根节点
  75. this.root = newRoot;
  76. }
  77. // 调用 insertNonFull 方法将键值插入到非满的节点中
  78. this.insertNonFull(this.root, key);
  79. }
  80. }
  81. /**
  82. * 在非满节点中插入一个键值对
  83. *
  84. * @param node 当前节点
  85. * @param key 待插入的键
  86. */
  87. insertNonFull(node, key) {
  88. // 获取节点最后一个键的索引
  89. let i = node.keys.length - 1;
  90. // 如果节点没有子节点
  91. if (node.children.length === 0) {
  92. // 当索引大于等于0且key小于当前索引对应的键时,索引减1
  93. while (i >= 0 && key < node.keys[i]) {
  94. i--;
  95. }
  96. // 在索引i+1的位置插入key
  97. node.keys.splice(i + 1, 0, key);
  98. } else {
  99. // 当索引大于等于0且key小于当前索引对应的键时,索引减1
  100. while (i >= 0 && key < node.keys[i]) {
  101. i--;
  102. }
  103. // 索引加1,指向当前key应该插入的位置
  104. i++;
  105. // 如果当前子节点的键数量等于最大度减1
  106. if (node.children[i].keys.length === (2 * this.degree) - 1) {
  107. // 对当前子节点进行分裂
  108. this.splitChild(node, i, node.children[i]);
  109. // 如果key大于当前索引对应的键
  110. if (key > node.keys[i]) {
  111. // 索引加1,指向分裂后应该插入的位置
  112. i++;
  113. }
  114. }
  115. // 在指定子节点中递归插入key
  116. this.insertNonFull(node.children[i], key);
  117. }
  118. }
  119. /**
  120. * 将子节点从父节点中拆分,生成新的子节点并插入到父节点的子节点数组中
  121. *
  122. * @param parent 父节点
  123. * @param index 父节点中子节点的索引
  124. * @param child 要拆分的子节点
  125. */
  126. splitChild(parent, index, child) {
  127. // 创建一个新的B+树节点
  128. let newChild = new BPlusTreeNode();
  129. // 计算中间索引
  130. let midIndex = Math.floor(this.degree - 1);
  131. // 将child节点的keys数组从midIndex+1处开始的部分赋值给newChild节点的keys
  132. newChild.keys = child.keys.splice(midIndex + 1);
  133. // 如果child节点有子节点
  134. if (child.children.length !== 0) {
  135. // 将child节点的children数组从midIndex+1处开始的部分赋值给newChild节点的children
  136. newChild.children = child.children.splice(midIndex + 1);
  137. }
  138. // 将child节点的keys数组中midIndex位置的元素插入到parent节点的keys数组的index位置
  139. parent.keys.splice(index, 0, child.keys[midIndex]);
  140. // 将newChild节点插入到parent节点的children数组的index+1位置
  141. parent.children.splice(index + 1, 0, newChild);
  142. }
  143. }
  144. // 示例
  145. const bPlusTree = new BPlusTree(2);
  146. bPlusTree.insert(10);
  147. bPlusTree.insert(20);
  148. bPlusTree.insert(5);
  149. console.log(bPlusTree.search(10)); // BPlusTreeNode { keys: [ 10 ], children: [] }
  150. console.log(bPlusTree.search(15)); // BPlusTreeNode { keys: [ 10 ], children: [] }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/492207
推荐阅读
相关标签
  

闽ICP备14008679号