一、B+树定义

B+树定义:关键字个数比孩子结点个数小1的树。

除此之外B+树还有以下的要求:

  1. B+树包含2种类型的结点:内部结点(也称索引结点)和叶子结点。根结点本身即可以是内部结点,也可以是叶子结点。根结点的关键字个数最少可以只有1个。

  2. B+树与B树最大的不同是内部结点不保存数据,只用于索引,所有数据(或者说记录)都保存在叶子结点中。

  3. m阶B+树表示了内部结点最多有m-1个关键字(或者说内部结点最多有m个子树),阶数m同时限制了叶子结点最多存储m-1个记录。

  4. 内部结点中的key都按照从小到大的顺序排列,对于内部结点中的一个key,左树中的所有key都小于它,右子树中的key都大于等于它。叶子结点中的记录也按照key的大小排列。

  5. 每个叶子结点都存有相邻叶子结点的指针,叶子结点本身依关键字的大小自小而大顺序链接。

二、B+树的插入操作

1)若为空树,创建一个叶子结点,然后将记录插入其中,此时这个叶子结点也是根结点,插入操作结束。

2)针对叶子类型结点:根据key值找到叶子结点,向这个叶子结点插入记录。插入后,若当前结点key的个数小于等于m-1,则插入结束。否则将这个叶子结点分裂成左右两个叶子结点,左叶子结点包含前m/2个记录,右结点包含剩下的记录,将第m/2+1个记录的key进位到父结点中(父结点一定是索引类型结点),进位到父结点的key左孩子指针向左结点,右孩子指针向右结点。将当前结点的指针指向父结点,然后执行第3步。

3)针对索引类型结点:若当前结点key的个数小于等于m-1,则插入结束。否则,将这个索引类型结点分裂成两个索引结点,左索引结点包含前(m-1)/2个key,右结点包含m-(m-1)/2个key,将第m/2个key进位到父结点中,进位到父结点的key左孩子指向左结点, 进位到父结点的key右孩子指向右结点。将当前结点的指针指向父结点,然后重复第3步。

下面是一颗5阶B树的插入过程,5阶B数的结点最少2个key,最多4个key。

a)空树中插入5

b)依次插入8,10,15

c)插入16

插入16后超过了关键字的个数限制,所以要进行分裂。在叶子结点分裂时,分裂出来的左结点2个记录,右边3个记录,中间key成为索引结点中的key,分裂后当前结点指向了父结点(根结点)。结果如下图所示。

当然我们还有另一种分裂方式,给左结点3个记录,右结点2个记录,此时索引结点中的key就变为15。

d)插入17

e)插入18,插入后如下图所示

当前结点的关键字个数大于5,进行分裂。分裂成两个结点,左结点2个记录,右结点3个记录,关键字16进位到父结点(索引类型)中,将当前结点的指针指向父结点。

当前结点的关键字个数满足条件,插入结束

f)插入若干数据后

g)在上图中插入7,结果如下图所示

当前结点的关键字个数超过4,需要分裂。左结点2个记录,右结点3个记录。分裂后关键字7进入到父结点中,将当前结点的指针指向父结点,结果如下图所示。

当前结点的关键字个数超过4,需要继续分裂。左结点2个关键字,右结点2个关键字,关键字16进入到父结点中,将当前结点指向父结点,结果如下图所示。

当前结点的关键字个数满足条件,插入结束。

三、B+树的删除操作

如果叶子结点中没有相应的key,则删除失败。否则执行下面的步骤

1)删除叶子结点中对应的key。删除后若结点的key的个数大于等于Math.ceil(m-1)/2 – 1,删除操作结束,否则执行第2步。

2)若兄弟结点key有富余(大于Math.ceil(m-1)/2 – 1),向兄弟结点借一个记录,同时用借到的key替换父结(指当前结点和兄弟结点共同的父结点)点中的key,删除结束。否则执行第3步。

3)若兄弟结点中没有富余的key,则当前结点和兄弟结点合并成一个新的叶子结点,并删除父结点中的key(父结点中的这个key两边的孩子指针就变成了一个指针,正好指向这个新的叶子结点),将当前结点指向父结点(必为索引结点),执行第4步(第4步以后的操作和B树就完全一样了,主要是为了更新索引结点)。

4)若索引结点的key的个数大于等于Math.ceil(m-1)/2 – 1,则删除操作结束。否则执行第5步

5)若兄弟结点有富余,父结点key下移,兄弟结点key上移,删除结束。否则执行第6步

6)当前结点和兄弟结点及父结点下移key合并成一个新的结点。将当前结点指向父结点,重复第4步。

注意,通过B+树的删除操作后,索引结点中存在的key,不一定在叶子结点中存在对应的记录。

下面是一颗5阶B树的删除过程,5阶B数的结点最少2个key,最多4个key。

a)初始状态

b)删除22,删除后结果如下图

删除后叶子结点中key的个数大于等于2,删除结束

c)删除15,删除后的结果如下图所示

删除后当前结点只有一个key,不满足条件,而兄弟结点有三个key,可以从兄弟结点借一个关键字为9的记录,同时更新将父结点中的关键字由10也变为9,删除结束。

d)删除7,删除后的结果如下图所示

当前结点关键字个数小于2,(左)兄弟结点中的也没有富余的关键字(当前结点还有个右兄弟,不过选择任意一个进行分析就可以了,这里我们选择了左边的),所以当前结点和兄弟结点合并,并删除父结点中的key,当前结点指向父结点。

此时当前结点的关键字个数小于2,兄弟结点的关键字也没有富余,所以父结点中的关键字下移,和两个孩子结点合并,结果如下图所示。

四、Java代码实现

  1. public class BTreeNode {
  2. public BTreeNode parent;//父节点
  3. /*以升序方式存储.*/
  4. public List<Integer> keys;
  5. /*孩子*/
  6. public List<BTreeNode> children;
  7. public boolean leaf;//是否是子节点
  8. /*子节点中指向下一个节点.*/
  9. public BTreeNode next;
  10. public BTreeNode() {
  11. keys = new ArrayList<>();
  12. children = new ArrayList<>();
  13. leaf = false;
  14. }
  15. /*返回关键字个数*/
  16. public int size() {
  17. return keys.size();
  18. }
  19. //该节点中存储的索引是否包含该key值,包含则返回当前索引值,否则返回小于key值的索引
  20. public SearchResult searchKey(Integer key) {
  21. int index = Collections.binarySearch(keys, key);
  22. if (index >= 0) {
  23. return new SearchResult(index, true);
  24. } else {
  25. return new SearchResult(Math.abs(index + 1), false);
  26. }
  27. }
  28. //keys集合是升序排序,这里做了排序的动作,可以直接添加,然后对集合重新排序
  29. public void addKey(Integer key) {
  30. SearchResult searchResult = searchKey(key);
  31. if (!searchResult.found) {
  32. List<Integer> list = new ArrayList<>(size() + 1);
  33. for (int i = 0; i < searchResult.index; i++) {
  34. list.add(keys.get(i));
  35. }
  36. list.add(key);
  37. for (int i = searchResult.index; i < keys.size(); i++) {
  38. list.add(keys.get(i));
  39. }
  40. keys = list;
  41. }
  42. }
  43. //从集合中移除索引
  44. public void removeKey(Integer key) {
  45. keys.remove(key);
  46. }
  47. //获取子孩子
  48. public BTreeNode childAt(int index) {
  49. if (leaf) {
  50. throw new UnsupportedOperationException("Leaf node doesn't have children.");
  51. } else {
  52. return children.get(index);
  53. }
  54. }
  55. //将子孩子添加到集合末尾
  56. public void addChild(BTreeNode node) {
  57. children.add(node);
  58. }
  59. public void removeChild(int index) {
  60. children.remove(index);
  61. }
  62. //某个位置上的子孩子添加
  63. public void addChild(BTreeNode child, int index) {
  64. List<BTreeNode> newChildren = new ArrayList<>();
  65. int i = 0;
  66. for (; i < index; ++i) {
  67. newChildren.add(children.get(i));
  68. }
  69. newChildren.add(child);
  70. for (; i < children.size(); ++i) {
  71. newChildren.add(children.get(i));
  72. }
  73. children = newChildren;
  74. }
  75. }
  76. public class SearchResult {
  77. public int index;//索引所在集合的位置
  78. public boolean found;//是否找到索引
  79. public SearchResult() {
  80. }
  81. public SearchResult(int index, boolean found) {
  82. this.index = index;
  83. this.found = found;
  84. }
  85. }
  86. public class Result extends SearchResult {
  87. public BTreeNode node;//当前节点,索引值没有找到,则为要插入的节点
  88. public int parentIndex;//当前节点在父级节点的位置
  89. public Result(BTreeNode node, int index, int parentIndex, boolean found) {
  90. super(index, found);
  91. this.node = node;
  92. this.parentIndex = parentIndex;
  93. }
  94. public Result(BTreeNode node, int parentIndex, SearchResult searchResult) {
  95. super(searchResult.index, searchResult.found);
  96. this.node = node;
  97. this.parentIndex = parentIndex;
  98. }
  99. }

上面的基本定义则描述了一个节点包括其索引集合,还包括其子孩子,并且在BTreeNode中封装了一些方法,供后续调用

  1. public class BTree {
  2. private static final int DEFAULT_T = 2;
  3. public BTreeNode root;
  4. /* 根据B树的定义,B树的每个非根节点的关键字数n满足(t - 1) <= n <= (2t - 1) */
  5. private int t = DEFAULT_T;
  6. /* 非根节点中最小的键值数 */
  7. private int minKeySize;
  8. /* 非根节点中最大的键值数 */
  9. private int maxKeySize;
  10. public BTree(int t /*传入b树的阶数*/) {
  11. this();
  12. this.t = t;
  13. minKeySize = t / 2;
  14. maxKeySize = t - 1;
  15. }
  16. }

封装方法,找到当前索引的位置,没有找到时,则返回索引所在的节点中集合的位置,

  1. public Result searchLeafNode(BTreeNode node, int parentIndex, Integer key) {
  2. SearchResult searchResult = node.searchKey(key);
  3. if (node.leaf) {//子节点
  4. return new Result(node, parentIndex, searchResult);
  5. } else {
  6. if (searchResult.found) {
  7. searchResult.index++;
  8. }
  9. return searchLeafNode(node.children.get(searchResult.index), searchResult.index, key);
  10. }
  11. }

插入思路:

  1. 找到关键字的位置,找到该节点一定是子节点。
  2. 添加了关键字的节点,判断是否满了,满了则进行分裂
  3. 子节点分裂时,选取中间值上升为父节点中值,但不从子节点中移除,因为子节点保存关键字的值,非子节点保存仅仅是索引
  1. public boolean insert(Integer key) {
  2. // 找到子节点
  3. Result result = searchLeafNode(root, 0, key);
  4. if (result.found) {//找到该节点,不操作
  5. return false;
  6. }
  7. BTreeNode node = result.node;
  8. node.addKey(key);
  9. //判断节点是否满了,满了则进行分割
  10. if (isFull(node)) {
  11. split(node.parent, result.parentIndex, node);
  12. }
  13. return true;
  14. }
  15. //进行分割
  16. public void split(BTreeNode parentNode, int parentIndex, BTreeNode childNode) {
  17. //将当前节点一份为二,小的部分将放入到新节点中,自身则成为右节点
  18. int mid = childNode.size() / 2;
  19. Integer key = null;
  20. boolean unLeaf = childNode.children.isEmpty();
  21. //判断是否为子节点,如果是子节点,索引会放入到右节点中,否则会放入到父节点中
  22. if (unLeaf) {
  23. key = childNode.keys.get(mid);
  24. } else {
  25. key = childNode.keys.remove(mid);
  26. }
  27. //分裂出左节点形成新的节点
  28. List<Integer> keys = new ArrayList<>();
  29. for (int i = 0; i < mid; i++) {
  30. Integer k = childNode.keys.remove(0);
  31. keys.add(k);
  32. }
  33. BTreeNode node = new BTreeNode();
  34. node.parent = parentNode;
  35. node.leaf = childNode.children.isEmpty();
  36. node.keys.addAll(keys);
  37. node.next = childNode;//节点下一个
  38. //将孩子节点部分也移动到新节点中
  39. if (!unLeaf) {
  40. mid = childNode.children.size() / 2;
  41. for (int i = 0; i < mid; i++) {
  42. BTreeNode bTreeNode = childNode.children.remove(0);
  43. bTreeNode.parent = node;
  44. node.addChild(bTreeNode);
  45. }
  46. }
  47. //父节点为空时,需要产生一个新节点
  48. if (parentNode == null) {
  49. root = new BTreeNode();
  50. root.leaf = false;
  51. parentNode = root;
  52. childNode.parent = parentNode;
  53. node.parent = parentNode;
  54. parentNode.children.add(childNode);
  55. }
  56. int index = parentNode.addKey(key);
  57. //前一个指针的下一个指针重新定向
  58. BTreeNode preNode = parentNode.children.get(parentIndex);
  59. preNode.next = node;
  60. //将节点添加到列表中
  61. parentNode.addChild(node, index);
  62. if (isFull(parentNode)) {//父节点终索引是否满了,满了,则继续分裂
  63. split(parentNode.parent, 0, parentNode);
  64. }
  65. }
  66. private boolean isFull(BTreeNode node) {
  67. return node.size() > maxKeySize;
  68. }

删除关键字思路:

  1. 找到该节点,如果是为找到,直接返回
  2. 找到该节点,出现一定是在子节点中,移除掉后,判断子节点的索引值是否大于最小数,大于则返回,否则需要进行合并。移除的当前节点如果出现在父节点中,也需要移除。会从子节点中选择一个节点进行补充
  3. 删除后小于最小数,则先从兄弟节点借,如果兄弟节点借不出,则进行合并
  4. 子节点进行合并,不需要移动子孩子
  5. 父节点进行合并,需要将孩子节点移动
  1. //删除节点
  2. public boolean delete(Integer key) {
  3. //找到该节点
  4. Result result = searchLeafNode(root, 0, key);
  5. if (!result.found) {//未找到
  6. return false;
  7. }
  8. //删除的节点数量大于
  9. BTreeNode node = result.node;
  10. node.removeKey(key);
  11. if (node.keys.size() >= minKeySize) {
  12. if (node.parent.keys.contains(key)) {//父节点中包含该节点
  13. Integer min = node.keys.get(0);
  14. node.parent.removeKey(key);
  15. node.parent.addKey(min);
  16. }
  17. return true;
  18. }
  19. //删除节点后,不满足情况,则找兄弟节点借
  20. BTreeNode parent = node.parent;
  21. if (result.parentIndex != 0 && parent.children.get(result.parentIndex - 1).keys.size() > minKeySize) {//左节点有富余可以借
  22. BTreeNode left = parent.children.get(result.parentIndex - 1);
  23. Integer max = left.keys.remove(left.keys.size() - 1);
  24. //替换节点
  25. if (parent.keys.contains(key)) {
  26. parent.removeKey(key);
  27. parent.addKey(max);
  28. node.addKey(max);
  29. } else {
  30. Integer min = node.keys.get(0);
  31. parent.removeKey(min);
  32. parent.addKey(max);
  33. node.addKey(max);
  34. }
  35. } else if (result.parentIndex < parent.children.size() - 1 && parent.children.get(result.parentIndex - 1).keys.size() > minKeySize) {//右节点有富余可以借
  36. BTreeNode right = parent.children.get(result.parentIndex + 1);
  37. Integer min = right.keys.remove(0);
  38. //替换节点
  39. if (parent.keys.contains(key)) {
  40. parent.removeKey(key);
  41. parent.addKey(min);
  42. node.addKey(min);
  43. } else {
  44. Integer max = node.keys.get(node.keys.size() - 1);
  45. parent.removeKey(max);
  46. parent.addKey(min);
  47. node.addKey(min);
  48. }
  49. } else {
  50. //兄弟节点也没有,则进行合并
  51. node.parent.removeKey(key);
  52. node.parent.removeKey(node.keys.get(0));
  53. union(node, result.parentIndex);
  54. }
  55. return true;
  56. }
  57. public void union(BTreeNode node, int parentIndex) {
  58. int ch = 0;
  59. if (parentIndex == 0) {//当前节点是最左节点,则只能找右节点
  60. ch = 1;
  61. } else {//否则找左节点
  62. ch = parentIndex - 1;
  63. }
  64. BTreeNode parent = node.parent;
  65. if (parent == null) {
  66. return;
  67. }
  68. BTreeNode kNode = parent.children.get(ch);
  69. for (int i = 0; i < node.size(); i++) {
  70. kNode.addKey(node.keys.get(i));
  71. }
  72. parent.removeChild(parentIndex);//移除节点
  73. //判断上级节点
  74. if (parent.keys.size() < minKeySize) {
  75. union(parent);
  76. }
  77. }
  78. public void union(BTreeNode node) {
  79. if (node.parent == null) {
  80. return;
  81. }
  82. Integer min = node.keys.get(0);
  83. BTreeNode parent = node.parent;
  84. //找到当前节点的位置
  85. Integer index = -1;
  86. for (int i = parent.keys.size() - 1; i >= 0; i--) {
  87. if (min > parent.keys.get(i)) {
  88. index = i;
  89. break;
  90. }
  91. }
  92. Integer parentValue = null;
  93. if (index != -1) {
  94. parentValue = parent.keys.get(index);
  95. } else {//没有找到则表示当前节点为最左节点
  96. parentValue = parent.keys.get(index + 1);
  97. }
  98. if (index != -1 && parent.children.get(index).keys.size() > minKeySize) {
  99. //判断左节点是否富余
  100. BTreeNode left = parent.children.get(index);
  101. Integer max = left.keys.get(left.size() - 1);
  102. parent.keys.add(index, max);
  103. node.addKey(parentValue);
  104. node.addChild(left.children.get(left.children.size() - 1), 0);
  105. } else if ((index == -1 && parent.children.get(index + 2).keys.size() > minKeySize) || (index < parent.keys.size() - 1 && parent.children.get(index + 1).keys.size() > minKeySize)) {
  106. //判断右节点是否富余
  107. BTreeNode right = parent.children.get(index + 1);
  108. Integer m = right.keys.get(0);
  109. parent.keys.add(index, m);
  110. node.addKey(parentValue);
  111. node.addChild(right.children.get(0));
  112. } else {
  113. //合并
  114. if (index == -1) {
  115. //合并到右节点
  116. BTreeNode right = parent.children.get(index + 2);
  117. Integer pa = parent.keys.remove(index + 1);
  118. right.addKey(pa);
  119. for (int i = 0; i < node.keys.size(); i++) {
  120. right.addKey(node.keys.get(i));
  121. }
  122. List<BTreeNode> bTreeNodes = new ArrayList<>();
  123. for (int i = 0; i < node.children.size(); i++) {
  124. bTreeNodes.add(node.children.get(i));
  125. }
  126. for (int i = 0; i < right.children.size(); i++) {
  127. bTreeNodes.add(right.children.get(i));
  128. }
  129. right.children = bTreeNodes;
  130. parent.children.remove(index + 1);//移除该节点
  131. if (parent.keys.isEmpty()) {//节点为空
  132. root = right;
  133. return;
  134. }
  135. } else {
  136. //合并到左节点
  137. //找到父级节点下沉,并将该节点所有添加到左节点中
  138. BTreeNode left = parent.children.get(index);
  139. Integer max = parent.keys.remove(index.intValue());
  140. left.addKey(max);
  141. for (int i = 0; i < node.keys.size(); i++) {
  142. left.addKey(node.keys.get(i));
  143. }
  144. for (int i = 0; i < node.children.size(); i++) {
  145. left.children.add(node.children.get(i));
  146. }
  147. parent.children.remove(index + 1);//移除该节点
  148. if (parent.keys.isEmpty()) {//节点为空
  149. root = left;
  150. return;
  151. }
  152. }
  153. }
  154. //判断上级节点
  155. if (parent.keys.size() < minKeySize) {
  156. union(parent);
  157. }
  158. }

打印输出:

  1. private void outPut(BTreeNode node, int index) {
  2. if (node.leaf) {
  3. List<Integer> kes = node.keys;
  4. System.out.println("叶子节点,层级:" + index + ",keys:" + kes);
  5. } else {
  6. List<Integer> kes = node.keys;
  7. System.out.println("层级:" + index + ",keys:" + kes);
  8. for (int i = 0; i < node.children.size(); i++) {
  9. outPut(node.children.get(i), index + 1);
  10. }
  11. }
  12. }
  13. public static void main(String[] args) {
  14. BTree tree = new BTree(5);
  15. tree.insert(5);
  16. tree.insert(8);
  17. tree.insert(10);
  18. tree.insert(15);
  19. tree.insert(16);
  20. tree.insert(17);
  21. tree.insert(6);
  22. tree.insert(9);
  23. tree.insert(18);
  24. tree.insert(19);
  25. tree.insert(20);
  26. tree.insert(21);
  27. tree.insert(22);
  28. tree.insert(7);
  29. tree.outPut(tree.root, 0);
  30. System.out.println("---------------------------------------------");
  31. tree.delete(22);
  32. tree.delete(15);
  33. tree.outPut(tree.root, 0);
  34. System.out.println("---------------------------------------------");
  35. tree.delete(7);
  36. tree.outPut(tree.root, 0);
  37. System.out.println("---------------------------------------------");
  38. }

最后的结果:

  1. 层级:0,keys:[16]
  2. 层级:1,keys:[7, 10]
  3. 叶子节点,层级:2,keys:[5, 6]
  4. 叶子节点,层级:2,keys:[7, 8, 9]
  5. 叶子节点,层级:2,keys:[10, 15]
  6. 层级:1,keys:[18, 20]
  7. 叶子节点,层级:2,keys:[16, 17]
  8. 叶子节点,层级:2,keys:[18, 19]
  9. 叶子节点,层级:2,keys:[20, 21, 22]
  10. ---------------------------------------------
  11. 层级:0,keys:[16]
  12. 层级:1,keys:[7, 9]
  13. 叶子节点,层级:2,keys:[5, 6]
  14. 叶子节点,层级:2,keys:[7, 8]
  15. 叶子节点,层级:2,keys:[9, 10]
  16. 层级:1,keys:[18, 20]
  17. 叶子节点,层级:2,keys:[16, 17]
  18. 叶子节点,层级:2,keys:[18, 19]
  19. 叶子节点,层级:2,keys:[20, 21]
  20. ---------------------------------------------
  21. 层级:0,keys:[9, 16, 18, 20]
  22. 叶子节点,层级:1,keys:[5, 6, 8]
  23. 叶子节点,层级:1,keys:[9, 10]
  24. 叶子节点,层级:1,keys:[16, 17]
  25. 叶子节点,层级:1,keys:[18, 19]
  26. 叶子节点,层级:1,keys:[20, 21]

B+树 -- Java实现的更多相关文章

  1. AVL树----java

                                                                                        AVL树----java AVL ...

  2. 伸展树--java

    文字转载自:http://www.cnblogs.com/vamei 代码转载自:http://www.blogjava.net/javacap/archive/2007/12/19/168627.h ...

  3. B树Java代码实现以及测试

    B树定义 B 树又叫平衡多路查找树.一棵m阶的B 树 (m叉树)的特性如下: 根节点至少有两个孩子 每个非根节点至少有M/2(上取整)个孩子,至多有M个孩子. 每个非根节点至少有M/2-1(上取整)个 ...

  4. 赫夫曼树JAVA实现及分析

    一,介绍 1)构造赫夫曼树的算法是一个贪心算法,贪心的地方在于:总是选取当前频率(权值)最低的两个结点来进行合并,构造新结点. 2)使用最小堆来选取频率最小的节点,有助于提高算法效率,因为要选频率最低 ...

  5. 字典树Java实现

    Trie树的原理 Trie树也称字典树,因为其效率很高,所以在在字符串查找.前缀匹配等中应用很广泛,其高效率是以空间为代价的. 利用串构建一个字典树,这个字典树保存了串的公共前缀信息,因此可以降低查询 ...

  6. P1047_校门外的树(JAVA语言)

    题目描述 某校大门外长度为L的马路上有一排树,每两棵相邻的树之间的间隔都是1米. 我们可以把马路看成一个数轴,马路的一端在数轴0的位置,另一端在L的位置: 数轴上的每个整数点,即0,1,2,-,L都种 ...

  7. 最小高度树Java版本(力扣)

    最小高度树 给定一个有序整数数组,元素各不相同且按升序排列,编写一个算法,创建一棵高度最小的二叉搜索树. 示例:给定有序数组: [-10,-3,0,5,9],一个可能的答案是:[0,-3,9,-10, ...

  8. ✡ leetcode 156. Binary Tree Upside Down 旋转树 --------- java

    156. Binary Tree Upside Down Add to List QuestionEditorial Solution My Submissions   Total Accepted: ...

  9. AVL树(Java实现)

    AVL树基本介绍 AVL树是一种自平衡的二叉查找树,在AVL树中任何节点的两个子树的高度差不能超过1.就是相当于在二叉搜索树的基础上,在插入和删除时进行了平衡处理. 不平衡的四种情况 LL:结构介绍 ...

随机推荐

  1. Postman - 測試 API 的好工具

    POSTMAN in Google APP Store 因為工作的關係,常常寫一些 API 供 APP 使用.以前傻傻的,每次測試的時候都會自己刻一個 HTML 的表單,一個一個填入 input ,接 ...

  2. hibernate中常用的Hql语句总结

    // HQL: Hibernate Query Language. // 特点: // >> 1,与SQL相似,SQL中的语法基本上都可以直接使用. // >> 2,SQL查询 ...

  3. session token防表单重提

    1.表单页面初始化前,先在session存入一个token值,随后把token存放在表单页面隐藏表单域内,开始初始化: 在表单页初始化前,调用ajax请求,在后台生成token,并返回至表单页 fun ...

  4. L-BFGS

    L-BFGS算法比较适合在大规模的数值计算中,具备牛顿法收敛速度快的特点,但不需要牛顿法那样存储Hesse矩阵,因此节省了大量的空间以及计算资源.本文主要通过对于无约束最优化问题的一些常用算法总结,一 ...

  5. 二、安装并配置Kubernetes Master节点

    1. 安装配置Master节点上的Kubernetes服务 1.1 安装Master节点上的Kubernetes服务 yum -y install kubernetes 1.2 修改kube-apis ...

  6. AndroidPn服务端部分bug解决方案

    目前推送的情况已经大致可以了,可以正常推送.但是要在实际生产中使用,要改进很多地方. 原本的版本,是不会对消息重新发送的.消息如果丢失,或者用户没有在线,消息也不会重新的发送.所以,这些问题都是要解决 ...

  7. jvm linux 时区设置

    # 背景 在接入集团一个平台的时候,发现录制某个接口到测试环境回放,发现接口入参一致,一个start_day 一个end_day,但回放的时候会多调用一次数据库查询,很是奇怪: 查阅业务代码,发现确实 ...

  8. MSMQ理论+实践(上)

    关于MSMQ使用的文章,网上一搜一大把,为什么还要写呢?因为别人的终究是别人的,看一遍,只是过眼云烟罢了,还是要自己动手实践一下,才能把别人的变成自己的.再者就是网上大都是在一台电脑上写的demo,我 ...

  9. K8S+GitLab-自动化分布式部署ASP.NET Core(二) ASP.NET Core DevOps

    一.介绍 前一篇,写的K8S部署环境的文章,简单的介绍下DevOps(Development和Operations的组合词),高效交付, 自动化流程,来减少软件开发人员和运维人员的沟通.Martin ...

  10. ASP.NET Core学习总结(1)

    经过那么长时间的学习,终于想给自己这段时间的学习工作做个总结了.记得刚开始学习的时候,什么资料都没有,光就啃文档.不过,值得庆幸的是,自己总算还有一些Web开发的基础.至少ASP.NET的WebFor ...