数据结构与算法 - B树

一、概述

1. 历史

B树(B-Tree)结构是一种高效存储和查询数据的方法,它的历史可以追溯到1970年代早期。B树的发明人Rudolf Bayer和Edward M. McCreight分别发表了一篇论文介绍了B树。这篇论文是1972年发表于《ACM Transactions on Database Systems》中的,题目为"Organization and Maintenance of Large Ordered Indexes"。

这篇论文提出了一种能够高效地维护大型有序索引的方法,这种方法的主要思想是将每个节点扩展成多个子节点,以减少查找所需的次数。B树结构非常适合应用于磁盘等大型存储器的高效操作,被广泛应用于关系数据库和文件系统中。

B树结构有很多变种和升级版,例如B+树、B*树和SB树等。这些变种和升级版本都基于B树的核心思想,通过调整B树的参数和结构,提高了B树在不同场景下的性能表现。

总的来说,B树结构是一个非常重要的数据结构,为高效存储和查询大量数据提供了可靠的方法。它的历史可以追溯到上个世纪70年代,而且在今天仍然被广泛应用于各种场景。

2. B的含义

B树的名称是由其发明者Rudolf Bayer提出的。Bayer和McCreight从未解释B代表什么,人们提出了许多可能的解释,比如Boeing、balance、between、broad、bushy和Bayer等。但McCreight表示,越是思考B-trees中的B代表什么,就越能更好地理解B-trees。

3. 特性

一棵B-树具有以下性质

特性1:每个节点x具有

  • 属性n,表示节点x中key的个数
  • 属性leaf,表示节点是否是叶子节点
  • 节点key可以有多个,以升序存储

特性2:每个非叶子节点中的孩子数是n + 1、叶子节点没有孩子

特性3:最小度数t(节点的孩子数称为度)和节点中键数量的关系如下:

最小度数t 键数量范围
2 1 ~ 3
3 2 ~ 5
4 3 ~ 7
... ...
n (n-1) ~ (2n-1)

其中,当节点中键数量达到其最大值时,即3、5、7··· 2n - 1,需要分裂

特性4:叶子节点的深度都相同

问题1:B-树为什么有最小度数的限制?

答:B树种有最小度数的限制是为了保证B树的平衡特性。

在B树中,每个节点都可以有多个子节点,这使得B树可以存储大量的键值,但也带来了一些问题。如果节点的子节点数量太少,那么就可能导致B树的高度过高,从而降低了B树的效率。此外,如果节点的子节点数量太多,那么就可能导致节点的搜索、插入和删除操作变得复杂和低效。

最小度数的限制通过限制节点的子节点数量,来平衡这些问题。在B树种,每个节点的子节点数量都必须在一定的范围内,即t到2t之间(其中t为最小度数)

4. B-树与2-3树、2-3-4树的关系

可以这样总计它们之间的关系:

  • 2-3树是最小度数为2的B树,其中每个节点可以包含2个或3个子节点
  • 2-3-4树是最小度数为2的B树的一种特殊情况,其中每个节点可以包含2个、3个或4个子节点
  • B树是一种更加一般化的平衡树,可以适应不同的应用场景,其节点可以包含任意数量的键值,节点的度数取决于最小度数t的设定。

二、实现

1. 定义节点

java 复制代码
static class Node {
    boolean leaf = true;
    int keyNumber;
    int t;
    int[] keys;
    Node[] children;    

    public Node(int t) {
        this.t = t;
        this.keys = new int[2 * t - 1];
        this.children = new Node[2 * t];
    }
    
    @Override
    public String toString() {
        return Arrays.toString(Arrays.copyOfRange(keys, 0, keyNumber));
    }
}
  • leaf表示是否是叶子节点
  • keyNumber为keys中有效key数目
  • t为最小度数,它决定了节点中key的最小、最大数目,分别是t - 1 和 2t - 1
  • keys存储此节点的key
  • children存储此节点的child
  • toString只是为了方便调试和测试,非必须
  • 实际keys应当改为entries以便同时保存key和value,刚开始简化实现

2. 多路查找

为上面节点添加get方法

java 复制代码
        /**
         * 多路查找
         * @param key
         * @return
         */
        public Node get(int key) {
            int i = 0;
            while(i < keyNumber) {
                if(keys[i] == key) {
                    return this;
                }
                if(keys[i] > key) {
                    break;
                }
                i++;
            }
            // 执行到此时,keys[i] > key 或 i==keyNumber
            if(leaf) {
                return null;
            }
            // 非叶子节点情况
            return children[i].get(key);
        }

3. 插入key和child

为上面节点类添加insertKey和insertChild方法

java 复制代码
        /**
         * 向keys指定索引处插入key
         * @param key
         * @param index
         */
        public void insertKey(int key, int index) {
            System.arraycopy(keys, index, keys, index + 1, keyNumber - index);
            keys[index] = key;
            keyNumber++;
        }

        /**
         * 向children指定索引处插入child
         * @param child
         * @param index
         */
        public void insertChild(Node child, int index) {
            System.arraycopy(children, index, children, index + 1, keyNumber - index);
            children[index] = child;
        }

作用是向keys数组或children数组指定index处插入新数据,注意

①由于使用了静态数组,并且不会在新增或删除时改变它的大小,因此需要额外的keyNumber来指定数组内有效key的数目

  • 插入时keyNumber++
  • 删除时减少keyNumber的值即可

②children不会单独维护数目,它比keys多一个

③如果这两个方法同时调用,注意它们的先后顺序,insertChild后调用,因为它计算复制元素时用到了keyNumber

4. 定义树

java 复制代码
public class BTree {
    final int t;
    final int MIN_KEY_NUMBER;
    final int MAX_KEY_NUMBER;
    Node root;

    public BTree() {
        this(2);
    }

    public BTree(int t) {
        this.t = t;
        MIN_KEY_NUMBER = t - 1;
        MAX_KEY_NUMBER = 2 * t - 1;
        root = new Node(t);
    }
}

5. 插入

java 复制代码
    /**
     * 新增
     * @param key
     */
    public void put(int key) {
        doPut(root, key, null, 0);
    }

    private void doPut(Node node, int key, Node parent, int index) {
        // 1. 查找本节点的插入位置i
        int i = 0;
        while(i < node.keyNumber) {
            if(node.keys[i] == key) {
                // 更新
                return;
            }
            if(node.keys[i] > key) {
                break;  // 找到插入位置,即为此时的i
            }
            i++;
        }
        // 2. 如果节点是叶子节点,可以直接插入了
        if(node.leaf) {
            node.insertKey(key, i);
            // 上限
        }
        // 3. 如果节点是非叶子节点,需要在children[i]处继续递归插入
        else {
            doPut(node.children[i], key, node, i);
            // 上限
        }
        if(isFull(node)) {
            split(node, parent, index);
        }
    }

    boolean isFull(Node node) {
        return node.keyNumber == MAX_KEY_NUMBER;
    }

首先查找本节点中的插入位置i,如果没有空位(key被找到),应该走更新的逻辑,目前什么没做

接下来分两种情况:

  • 如果节点是叶子节点,可以直接插入了
  • 如果节点是非叶子节点,需要在children[i]处继续递归插入

无论哪种情况,插入完成后都可能超过节点keys数目限制,此时应当执行节点分裂

  • 参数中的parent和index都是给分裂方法用的,代表当前节点父节点,和分裂节点都是第几个孩子

判断依据为:

java 复制代码
boolean isFull(Node node) {
    return node.keyNumber == MAX_KEY_NUMBER;
}

6. 分裂

java 复制代码
    /**
     * 分裂
     * @param left 要分裂的节点
     * @param parent 分裂节点的父节点
     * @param index 分裂节点是第几个孩子
     */
    private void split(Node left, Node parent, int index) {
        // 分裂节点为根节点
        if(parent == null) {
            Node newRoot = new Node(t);
            newRoot.leaf = false;
            newRoot.insertChild(left, 0);
            this.root = newRoot;
            parent = newRoot;
        }

        // 1. 创建right节点,把left节点中t之后的key和child移动过去
        Node right = new Node(t);
        // 新增节点是否是叶子节点与待分裂节点一致
        right.leaf = left.leaf;
        System.arraycopy(left.keys, t, right.keys, 0, t - 1);
        // 如果分裂节点为非叶子节点
        if(!left.leaf) {
            System.arraycopy(left.children, t, right.children, 0, t);
        }
        right.keyNumber = t - 1;
        left.keyNumber = t - 1;

        // 2. 中间的key(t - 1处)插入到父节点
        int mid = left.keys[t - 1];
        parent.insertKey(mid, index);

        // 3. right节点作为父节点的孩子
        parent.insertChild(right, index + 1);
    }

分为两种情况:

①如果parent == null,表示要分裂的是根节点,此时需要创建新根,原来的根节点作为新根的0孩子

②否则

  • 创建right节点(分裂后大于当前left节点的)把t以后的key和child都拷贝过去
  • t - 1处的key插入到parent的index处,index指left作为孩子时的索引
  • right节点作为parent的孩子插入到index+1处

7. 删除

case 1:当前节点是叶子节点,没找到

case 2:当前节点是叶子节点,找到了

case 3:当前节点是非叶子节点,没找到

case 4:当前节点是非叶子节点,找到了

case 5:删除后key数目 < 下限(不平衡)

case 6:根节点

Node节点类添加一些方法:

java 复制代码
        /**
         * 向keys指定索引处插入key
         * @param key
         * @param index
         */
        public void insertKey(int key, int index) {
            System.arraycopy(keys, index, keys, index + 1, keyNumber - index);
            keys[index] = key;
            keyNumber++;
        }

        /**
         * 向children指定索引处插入child
         * @param child
         * @param index
         */
        public void insertChild(Node child, int index) {
            System.arraycopy(children, index, children, index + 1, keyNumber - index);
            children[index] = child;
        }

        /**
         * 移除指定index处的key
         * @param index
         * @return
         */
        int removeKey(int index) {
            int t = keys[index];
            System.arraycopy(keys, index + 1, keys, index, --keyNumber - index);
            return t;
        }

        /**
         * 移除最左边的key
         * @return
         */
        public int removeLeftmostKey() {
            return removeKey(0);
        }

        /**
         * 移除最右边的key
         * @return
         */
        public int removeRightmostKey() {
            return removeKey(keyNumber - 1);
        }

        /**
         * 移除指定index处的child
         * @param index
         * @return
         */
        public Node removeChild(int index) {
            Node t = children[index];
            System.arraycopy(children, index + 1, children, index, keyNumber - index);
            children[keyNumber] = null;  // help GC
            return t;
        }

        /**
         * 移除最左边的child
         * @return
         */
        public Node removeLeftmostChild() {
            return removeChild(0);
        }


        /**
         * 移除最右边的child
         * @return
         */
        public Node removeRightmostChild() {
            return removeChild(keyNumber);
        }

        /**
         * index 孩子处左边的兄弟
         * @param index
         * @return
         */
        public Node childLeftSibling(int index) {
            return index > 0 ? children[index - 1] : null;
        }

        /**
         * index 孩子处右边的兄弟
         * @param index
         * @return
         */
        public Node childRightSibling(int index) {
            return index == keyNumber ? null : children[index + 1];
        }

        /**
         * 复制当前节点的所有key和child到target
         * @param target
         */
        public void moveToTarget(Node target) {
            int start = target.keyNumber;
            if(!leaf) {
                for (int i = 0; i <= keyNumber; i++) {
                    target.children[start + i] = children[i];
                }
            }
            for (int i = 0; i < keyNumber; i++) {
                target.keys[target.keyNumber++] = keys[i];
            }
        }

删除代码:

java 复制代码
    /**
     * 删除key
     * @param key
     */
    public void remove(int key) {
        doRemove(null, root, 0, key);
    }

    private void doRemove(Node parent, Node node, int index, int key) {
        int i = 0;
        // 在有效范围内
        while(i < node.keyNumber) {
            if(node.keys[i] >= key) {
                break;
            }
            i++;
        }
        // 情况1:找到,i代表待删除key的索引
        // 情况2:没找到,i代表到第i个孩子继续查找
        if (node.leaf) {  // 当前节点是叶子节点
            if(!found(node,key, i)) {  // case 1 没找到
                return;
            } else {  // case 2 找到了
                node.removeKey(i);
            }
        } else {  // 当前节点不是叶子节点
            if(!found(node,key, i)) {  // case 3 没找到
                // 到孩子节点继续查找
                doRemove(node, node.children[i], i, key);
            } else {  // case 4 找到了
                // 1. 找后继key
                Node s = node.children[i + 1];  // 当前节点的后一个孩子
                while(!s.leaf) {
                    // 直到叶子节点,取最左边的
                    s = s.children[0];
                }
                int skey = s.keys[0];
                // 2. 替换待删除key
                node.keys[i] = skey;
                // 3. 删除后继key
                doRemove(node, node.children[i + 1], i + 1, skey);
            }
        }
        // 删除后key数目小于下限
        if(node.keyNumber < MIN_KEY_NUMBER) {
            // 调整平衡 case 5 case 6
            balance(parent, node, index);
        }

    }

    /**
     * 是否找到key
     * @param node
     * @param key
     * @param i
     * @return
     */
    private boolean found(Node node, int key, int i) {
        return i < node.keyNumber && node.keys[i] == key;
    }

    /**
     * 调整平衡
     * @param parent 父节点
     * @param x 待调整节点
     * @param i 索引
     */
    private void balance(Node parent, Node x, int i) {
        // case 6 根节点
        if(x == root) {
            if(root.keyNumber == 0 && root.children[0] != null) {
                root = root.children[0];
            }
            return;
        }
        // 获取左右两边的兄弟
        Node left = parent.childLeftSibling(i);
        Node right = parent.childRightSibling(i);

        if(left != null && left.keyNumber > MIN_KEY_NUMBER) {
            // case 5-1 左边富裕,右旋
            // a) 父节点中前驱key旋转下来
            x.insertKey(parent.keys[i - 1], 0);
            if(!left.leaf) {
                // b) 左边的兄弟不是叶子节点,把最右侧的孩子过继给被调整的节点
                x.insertChild(left.removeRightmostChild(), 0);
            }
            // c) 删除左边兄弟的最右节点,移到父节点(旋转上去)
            parent.keys[i - 1] = left.removeRightmostKey();
            return;
        }

        if(right != null && right.keyNumber > MIN_KEY_NUMBER) {
            // case 5-2 右边富裕,左旋
            // a) 父节点中后去key旋转下来
            x.insertKey(parent.keys[i], x.keyNumber);
            if(!right.leaf) {
                // b) 右边的兄弟不是叶子节点,把最左侧的孩子过继给被调整的节点
                x.insertChild(right.removeLeftmostChild(), x.keyNumber + 1);
            }
            // c) 删除右边兄弟的最左节点,移到父节点(旋转上去)
            parent.keys[i] = right.removeLeftmostKey();
            return;
        }

        // case 5-3 两边都不富裕,向左合并
        if(left != null) {
            // 向左兄弟合并
            // 将待删除节点从父节点移除
            parent.removeChild(i);
            // 从父节点合并一个key到左兄弟
            left.insertKey(parent.removeKey(i - 1), left.keyNumber);
            // 将待删除节点的剩余节点和孩子移到到左边
            x.moveToTarget(left);
        } else {
            // 没有左兄弟,向自己合并
            // 把它的右兄弟移除
            parent.removeChild(i + 1);
            // 父节点移除一个key,插入到待删除节点
            x.insertKey(parent.removeKey(i), x.keyNumber);
            // 将右兄弟合并过来
            right.moveToTarget(x);
        }
    }

8. 完整代码

java 复制代码
package com.itheima.datastructure.BTree;

import java.util.Arrays;

public class BTree {

    static class Node {
        int[] keys;  // 关键字
        Node[] children;  // 孩子
        int keyNumber;  // 有效关键字数目
        boolean leaf = true;  // 是否是叶子节点
        int t; // 最小度数

        public Node(int t) {  // t >= 2
            this.t = t;
            this.children = new Node[2 * t];
            this.keys = new int[2 * t - 1];
        }

        public Node(int[] keys) {
            this.keys = keys;
        }

        @Override
        public String toString() {
            return Arrays.toString(Arrays.copyOfRange(keys, 0, keyNumber));
        }

        /**
         * 多路查找
         * @param key
         * @return
         */
        public Node get(int key) {
            int i = 0;
            while(i < keyNumber) {
                if(keys[i] == key) {
                    return this;
                }
                if(keys[i] > key) {
                    break;
                }
                i++;
            }
            // 执行到此时,keys[i] > key 或 i==keyNumber
            if(leaf) {
                return null;
            }
            // 非叶子节点情况
            return children[i].get(key);
        }

        /**
         * 向keys指定索引处插入key
         * @param key
         * @param index
         */
        public void insertKey(int key, int index) {
            System.arraycopy(keys, index, keys, index + 1, keyNumber - index);
            keys[index] = key;
            keyNumber++;
        }

        /**
         * 向children指定索引处插入child
         * @param child
         * @param index
         */
        public void insertChild(Node child, int index) {
            System.arraycopy(children, index, children, index + 1, keyNumber - index);
            children[index] = child;
        }

        /**
         * 移除指定index处的key
         * @param index
         * @return
         */
        int removeKey(int index) {
            int t = keys[index];
            System.arraycopy(keys, index + 1, keys, index, --keyNumber - index);
            return t;
        }

        /**
         * 移除最左边的key
         * @return
         */
        public int removeLeftmostKey() {
            return removeKey(0);
        }

        /**
         * 移除最右边的key
         * @return
         */
        public int removeRightmostKey() {
            return removeKey(keyNumber - 1);
        }

        /**
         * 移除指定index处的child
         * @param index
         * @return
         */
        public Node removeChild(int index) {
            Node t = children[index];
            System.arraycopy(children, index + 1, children, index, keyNumber - index);
            children[keyNumber] = null;  // help GC
            return t;
        }

        /**
         * 移除最左边的child
         * @return
         */
        public Node removeLeftmostChild() {
            return removeChild(0);
        }


        /**
         * 移除最右边的child
         * @return
         */
        public Node removeRightmostChild() {
            return removeChild(keyNumber);
        }

        /**
         * index 孩子处左边的兄弟
         * @param index
         * @return
         */
        public Node childLeftSibling(int index) {
            return index > 0 ? children[index - 1] : null;
        }

        /**
         * index 孩子处右边的兄弟
         * @param index
         * @return
         */
        public Node childRightSibling(int index) {
            return index == keyNumber ? null : children[index + 1];
        }

        /**
         * 复制当前节点的所有key和child到target
         * @param target
         */
        public void moveToTarget(Node target) {
            int start = target.keyNumber;
            if(!leaf) {
                for (int i = 0; i <= keyNumber; i++) {
                    target.children[start + i] = children[i];
                }
            }
            for (int i = 0; i < keyNumber; i++) {
                target.keys[target.keyNumber++] = keys[i];
            }
        }
    }

    Node root;  // 根节点
    int t;  // 树中节点最小度数
    final int MIN_KEY_NUMBER;  // 最小key数目
    final int MAX_KEY_NUMBER;  // 最大key数目

    public BTree() {
        this(2);
    }

    public BTree(int t) {
        this.t = t;
        root = new Node(t);
        MAX_KEY_NUMBER = 2 * t - 1;
        MIN_KEY_NUMBER = t - 1;
    }

    /**
     * key是否存在
     * @param key
     * @return
     */
    public boolean contains(int key) {
        return root.get(key) != null;
    }

    /**
     * 新增
     * @param key
     */
    public void put(int key) {
        doPut(root, key, null, 0);
    }

    private void doPut(Node node, int key, Node parent, int index) {
        // 1. 查找本节点的插入位置i
        int i = 0;
        while(i < node.keyNumber) {
            if(node.keys[i] == key) {
                // 更新
                return;
            }
            if(node.keys[i] > key) {
                break;  // 找到插入位置,即为此时的i
            }
            i++;
        }
        // 2. 如果节点是叶子节点,可以直接插入了
        if(node.leaf) {
            node.insertKey(key, i);
            // 上限
        }
        // 3. 如果节点是非叶子节点,需要在children[i]处继续递归插入
        else {
            doPut(node.children[i], key, node, i);
            // 上限
        }
        if(isFull(node)) {
            split(node, parent, index);
        }
    }

    boolean isFull(Node node) {
        return node.keyNumber == MAX_KEY_NUMBER;
    }

    /**
     * 分裂
     * @param left 要分裂的节点
     * @param parent 分裂节点的父节点
     * @param index 分裂节点是第几个孩子
     */
    private void split(Node left, Node parent, int index) {
        // 分裂节点为根节点
        if(parent == null) {
            Node newRoot = new Node(t);
            newRoot.leaf = false;
            newRoot.insertChild(left, 0);
            this.root = newRoot;
            parent = newRoot;
        }

        // 1. 创建right节点,把left节点中t之后的key和child移动过去
        Node right = new Node(t);
        // 新增节点是否是叶子节点与待分裂节点一致
        right.leaf = left.leaf;
        System.arraycopy(left.keys, t, right.keys, 0, t - 1);
        // 如果分裂节点为非叶子节点
        if(!left.leaf) {
            System.arraycopy(left.children, t, right.children, 0, t);
        }
        right.keyNumber = t - 1;
        left.keyNumber = t - 1;

        // 2. 中间的key(t - 1处)插入到父节点
        int mid = left.keys[t - 1];
        parent.insertKey(mid, index);

        // 3. right节点作为父节点的孩子
        parent.insertChild(right, index + 1);
    }

    /**
     * 删除key
     * @param key
     */
    public void remove(int key) {
        doRemove(null, root, 0, key);
    }

    private void doRemove(Node parent, Node node, int index, int key) {
        int i = 0;
        // 在有效范围内
        while(i < node.keyNumber) {
            if(node.keys[i] >= key) {
                break;
            }
            i++;
        }
        // 情况1:找到,i代表待删除key的索引
        // 情况2:没找到,i代表到第i个孩子继续查找
        if (node.leaf) {  // 当前节点是叶子节点
            if(!found(node,key, i)) {  // case 1 没找到
                return;
            } else {  // case 2 找到了
                node.removeKey(i);
            }
        } else {  // 当前节点不是叶子节点
            if(!found(node,key, i)) {  // case 3 没找到
                // 到孩子节点继续查找
                doRemove(node, node.children[i], i, key);
            } else {  // case 4 找到了
                // 1. 找后继key
                Node s = node.children[i + 1];  // 当前节点的后一个孩子
                while(!s.leaf) {
                    // 直到叶子节点,取最左边的
                    s = s.children[0];
                }
                int skey = s.keys[0];
                // 2. 替换待删除key
                node.keys[i] = skey;
                // 3. 删除后继key
                doRemove(node, node.children[i + 1], i + 1, skey);
            }
        }
        // 删除后key数目小于下限
        if(node.keyNumber < MIN_KEY_NUMBER) {
            // 调整平衡 case 5 case 6
            balance(parent, node, index);
        }

    }

    /**
     * 是否找到key
     * @param node
     * @param key
     * @param i
     * @return
     */
    private boolean found(Node node, int key, int i) {
        return i < node.keyNumber && node.keys[i] == key;
    }

    /**
     * 调整平衡
     * @param parent 父节点
     * @param x 待调整节点
     * @param i 索引
     */
    private void balance(Node parent, Node x, int i) {
        // case 6 根节点不平衡
        if(x == root) {
            if(root.keyNumber == 0 && root.children[0] != null) {
                root = root.children[0];
            }
            return;
        }
        // 获取左右两边的兄弟
        Node left = parent.childLeftSibling(i);
        Node right = parent.childRightSibling(i);

        if(left != null && left.keyNumber > MIN_KEY_NUMBER) {
            // case 5-1 左边富裕,右旋
            // a) 父节点中前驱key旋转下来
            x.insertKey(parent.keys[i - 1], 0);
            if(!left.leaf) {
                // b) 左边的兄弟不是叶子节点,把最右侧的孩子过继给被调整的节点
                x.insertChild(left.removeRightmostChild(), 0);
            }
            // c) 删除左边兄弟的最右节点,移到父节点(旋转上去)
            parent.keys[i - 1] = left.removeRightmostKey();
            return;
        }

        if(right != null && right.keyNumber > MIN_KEY_NUMBER) {
            // case 5-2 右边富裕,左旋
            // a) 父节点中后去key旋转下来
            x.insertKey(parent.keys[i], x.keyNumber);
            if(!right.leaf) {
                // b) 右边的兄弟不是叶子节点,把最左侧的孩子过继给被调整的节点
                x.insertChild(right.removeLeftmostChild(), x.keyNumber + 1);
            }
            // c) 删除右边兄弟的最左节点,移到父节点(旋转上去)
            parent.keys[i] = right.removeLeftmostKey();
            return;
        }

        // case 5-3 两边都不富裕,向左合并
        if(left != null) {
            // 向左兄弟合并
            // 将待删除节点从父节点移除
            parent.removeChild(i);
            // 从父节点合并一个key到左兄弟
            left.insertKey(parent.removeKey(i - 1), left.keyNumber);
            // 将待删除节点的剩余节点和孩子移到到左边
            x.moveToTarget(left);
        } else {
            // 没有左兄弟,向自己合并
            // 把它的右兄弟移除
            parent.removeChild(i + 1);
            // 父节点移除一个key,插入到待删除节点
            x.insertKey(parent.removeKey(i), x.keyNumber);
            // 将右兄弟合并过来
            right.moveToTarget(x);
        }
    }

}

B站视频链接:基础算法-175-B树-remove-演示2_哔哩哔哩_bilibili

相关推荐
~yY…s<#>1 小时前
【刷题17】最小栈、栈的压入弹出、逆波兰表达式
c语言·数据结构·c++·算法·leetcode
XuanRanDev2 小时前
【每日一题】LeetCode - 三数之和
数据结构·算法·leetcode·1024程序员节
代码猪猪傻瓜coding2 小时前
力扣1 两数之和
数据结构·算法·leetcode
南宫生3 小时前
贪心算法习题其三【力扣】【算法学习day.20】
java·数据结构·学习·算法·leetcode·贪心算法
weixin_432702264 小时前
代码随想录算法训练营第五十五天|图论理论基础
数据结构·python·算法·深度优先·图论
passer__jw7675 小时前
【LeetCode】【算法】283. 移动零
数据结构·算法·leetcode
爱吃生蚝的于勒6 小时前
深入学习指针(5)!!!!!!!!!!!!!!!
c语言·开发语言·数据结构·学习·计算机网络·算法
羊小猪~~6 小时前
数据结构C语言描述2(图文结合)--有头单链表,无头单链表(两种方法),链表反转、有序链表构建、排序等操作,考研可看
c语言·数据结构·c++·考研·算法·链表·visual studio
脉牛杂德6 小时前
多项式加法——C语言
数据结构·c++·算法
一直学习永不止步7 小时前
LeetCode题练习与总结:赎金信--383
java·数据结构·算法·leetcode·字符串·哈希表·计数