BTree

BTree简介:

BTree特性:

BTree的节点:

该节点类写在BTree的内部,里面包含基本的属性和一些后面插入和删除时的常用方法

static class Node {
        int[] keys;//关键字
        Node[] children;//孩子
        int keyNumber;//有效关键字数目
        boolean leaf = true;//是否是叶子节点
        int t;//最小度数(最小孩子树)       --由我们指定

        public Node(int t) {//t>=2--最小孩子数为2
            this.t = t;
            this.children = new Node[2 * t];//最小度数*2==最多的孩子数--BTree约定
            this.keys = new int[2 * t - 1];
        }

        @Override
        public String toString() {
            //把拷贝完成的数组转化为字符串输出
            return Arrays.toString(Arrays.copyOfRange(keys, 0, keyNumber));
        }


        //多路查找
        Node get(int key) {
            //遍历
            int i = 0;
            while (i < keyNumber) {//有效范围内查询
                if (keys[i] == key) {
                    return this;
                }
//                if (keys[i]<key){//       <就继续循环
//                    i++;//继续向后找
//                }
                if (keys[i] > key) {//说明当前节点没找到
                    break;
                }
                i++;
            }
            //执行到此时  keys[i]>key     i==keyNumber[出了范围]
            //退出之后分两种情况:叶子节点,非叶子节点
            //叶子节点
            if (leaf) {
                return null;
            }
            //非叶子节点--退出的时候找到了比key大的值--根据退出循环时i的值,找到相应的孩子,继续进行次操作--所以递归
            return children[i].get(key);
        }

        //向keys指定索引处插入key
        void insertKey(int key, int index) {
            //从后往前移动元素
            System.arraycopy(keys, index, keys, index + 1, keyNumber - index);
            keys[index] = key;
            keyNumber++;
        }

        //向children指定索引处加入child
        void insertChild(Node child, int index) {
            //从后往前移动元素
            System.arraycopy(children, index, children, index + 1, keyNumber - index);
            children[index] = child;
        }

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

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

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

        // 移除指定 index 处的 child
        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
        Node removeLeftmostChild() {
            return removeChild(0);
        }

        // 移除最右边的 child
        Node removeRightmostChild() {
            //孩子数目比keys数目多1
            return removeChild(keyNumber);
        }

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

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

        // 复制当前节点的所有 key 和 child 到 target
        void moveToTarget(Node target) {
            int start = target.keyNumber;
            if (!leaf) {//如果当前孩子不是叶子节点,说明它有孩子,
                //就把当前节点的孩子,移动到target节点的孩子
                for (int i = 0; i <= keyNumber; i++) {
                    //target原来有的孩子不覆盖【保留】,把自己的孩子追加进去
                    target.children[start + i] = children[i];
                }
            }
            for (int i = 0; i < keyNumber; i++) {//key也追加进去
                target.keys[target.keyNumber++] = keys[i];
            }
        }
    }

BTree的插入:

插入:

split--分裂的分析:

        叶子节点:

        非叶子节点【比叶子节点多一个步骤,得处理它所带的孩子】:

根节点分裂【得创建出两个孩子,一个新root,一个right】

/**
     * 2.新增
     * 遇到叶子节点,直接加入key,如果key达到最大值,就执行分裂,重复这个过程、
     * 分裂:一个节点中的数目到达2t-1时,就要执行分裂
     */
    public void put(int key) {
        doPut(root, key, null, 0);
    }



    private void doPut(Node node, int key, Node parent, int index) {
        int i = 0;
        while (i < node.keyNumber) {
            if (node.keys[i] == key) {
                return;//更新
            }
            if (node.keys[i] > key) {
                break;//找到了插入位置,即为此时的i
            }
            i++;
        }
        if (node.leaf) {
            node.insertKey(key, i);
            //插入key之后,做一个检查,如果到达上限,那么就分裂
        } else {//非叶子节点,找孩子插入,就是在第i个孩子插入,走相同的逻辑,所以递归
            //要处理孩子节点,那么孩子节点的父亲,就是Node,要处理的孩子是第i个,所以传入的index==i
            doPut(node.children[i], key, node, i);
            //插入key之后,做一个检查,如果到达上限,那么就分裂
        }
        //插入key之后,做一个检查,如果到达上限,那么就分裂
        if (node.keyNumber == MAX_KEY_NUMBER) {
            split(node, parent, index);
        }
    }


    /**
     * 分裂方法的实现
     *
     * @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);//旧根节点作为新根节点的0孩子
            this.root = newRoot;
            parent = newRoot;
        }
        //1.创建right节点,把left中t之后的key和child移动过去
        Node right = new Node(t);//整个树的t都是一样的
        //如果待分裂节点是叶子节点,那么新创建的节点一定是叶子节点
        right.leaf = left.leaf;
        //复制之后,left.keys数组不会被破坏,但是有效索引会发送变化
        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;//更新right数组的keyNumber属性
        //实际上left中的keys[t-1]还在,只是打印时候不进行打印了
        left.keyNumber = t - 1;//更新left数组的keyNumber属性--只拿较小的那一半key
        //2.中间的key(t-1处)插入到父节点
        int mid = left.keys[t - 1];
        parent.insertKey(mid, index);
        //3.right节点为父节点的孩子
        parent.insertChild(right, index + 1);
    }

BTree的删除:

删除【删除BTree中某个节点的key,不是把节点给删了】:

一共分为6种情况:

case1:当前节点是叶子节点,没找到,直接返回

case2:当前节点是叶子节点,找到了,直接删除

case3:当前节点是非叶子节点,没找到,要继续到它的孩子里查找

case4:当前节点是非叶子节点,找到了,不能直接删,找到后继的key,然后用后继的key,替换掉待删除的key,删除动作变成要删除后继的key了【因为已经进行了替换,现在树中有俩后继】--效果相当于删了被删除节点

case5:非根节点:删除后key数目

        富余:node.keyNumber-1之后还>=MIN_KEY_NUMBER

        如果左边兄弟有富余的情况,那么可以来一次右旋--就可以恢复平衡

        如果右边兄弟有富余的情况,那么可以来一次左旋--就可以恢复平衡

        左右两边都不够借的时候,就得进行合并【目的是让节点的数目减少一个】:

        统一采用向左合并,位于右侧的节点就不要了,把他从父节点删掉,删掉之后把它和父节点的key统一合并到左侧节点上去【按升序顺序】--没有根了--特殊处理

case6:根节点--平衡调整时,针对根节点进行处理

        在做了一些操作【比如合并】,根节点的keys可能可变0,原来的根没必要保存,children[0]替换掉旧的根


    //3.删除
    /**
     * 删除【删除BTree中某个节点的key,不是把节点给删了】:
     * case1:当前节点是叶子节点,没找到,直接返回
     * case2:当前节点是叶子节点,找到了,直接删除
     * case3:当前节点是非叶子节点,没找到,要继续到它的孩子里查找
     * case4:当前节点是非叶子节点,找到了,不能直接删,找到后继的key,然后用后继的key,替换掉待删除的key,
     * 删除动作变成要删除后继的key了【因为已经进行了替换,现在树中有俩后继】--效果相当于删了被删除节点 左旋,右旋,合并
     * case5:非根节点--删除后key数目<下限【t-1】(不平衡)--要重新做平衡调整
     * case6:根节点--平衡调整时,针对根节点进行处理
     *
     * @param key
     */
    public void remove(int key) {
        //先从根节点,并且从0索引开始找
        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++;
        }
        //i 找到,代表待删除key的索引
        //i 没找到:代表到第i个孩子继续查找
        if (node.leaf) {//是叶子节点
            if (!found(node, key, i)) {//case1:当前节点是叶子节点,没找到,直接返回
                return;
            } else {//case2:当前节点是叶子节点,找到了,直接删除
                node.removeKey(i);//这里删完了不要return,因为还要检查是否<下限
            }
        } else {//不是叶子节点
            if (!found(node, key, i)) {//case3:当前节点是非叶子节点,没找到,要继续到它的孩子里查找
                doRemove(node, node.children[i], i, key);//进行递归调用即可
            } else {
                /*case4:当前节点是非叶子节点,找到了,不能直接删,找到后继的key,然后用后继的key,替换掉待删除的key,
                删除动作变成要删除后继的key了【因为已经进行了替换,现在树中有俩后继】--效果相当于删了被删除节点*/
                //怎么找后继key?
                //1.找到后继key
                Node s = node.children[i + 1];//比它大的孩子去找--s:后继key的起点
                while (!s.leaf) {//只要不是叶子节点,就一直向左走
                    s = s.children[0];
                }
                //这是s就是后继key所在的节点
                int sKey = s.keys[0];
                //2.替换待删除key
                node.keys[i] = sKey;
                //3.删除后继key--操作相同,进行递归--以i+1的孩子处作为起点--删的key为后继key--sKey
                doRemove(node, node.children[i + 1], i + 1, sKey);
            }
        }
        //到这儿已经删除完成了,就看是否平衡
        //判断key的数目是否<下限,<的话调整平衡
        if (node.keyNumber < MIN_KEY_NUMBER) {
            //调整平衡
            //case5:非根节点--删除后key数目<下限【t-1】(不平衡)--要重新做平衡调整--左旋,右旋,合并
            balance(parent, node, index);
            //case6:根节点--平衡调整时,针对根节点进行处理
        }
    }


    /**
     * 平衡BTree---下面是调整,不是删除
     *
     * @param parent 被调整节点的父亲
     * @param x      被调整节点
     * @param i      被调整节点是父亲的第i个孩子
     */
    private void balance(Node parent, Node x, int i) {
        //case 6 根节点
        if (x == root) {//根节点唯一不平衡的情况-->key的数目==0,或者孩子数==1
            if (root.keyNumber == 0 && root.children[0] != null) {
                root = root.children[0];
            }
            return;
        }
        Node left = parent.childLeftSibling(i);//左边兄弟
        Node right = parent.childRightSibling(i);//右边兄弟
        //case 5-1  左边富余,右旋
        if (left != null && left.keyNumber > MIN_KEY_NUMBER) {
            //a.右旋,把父节点【自左向右】最后一个比x小的旋转下去,旋转下去之后,在x中的索引为0
            x.insertKey(parent.keys[i - 1], 0);//parent.keys[i-1]--父节点中被旋转的元素,添加到被调整的节点中
            if (!left.leaf) {//说明左兄弟有孩子--要进行换孩子
                //b.把左兄弟最右侧的孩子给被调整节点x的最左侧
                x.insertChild(left.removeRightmostChild(), 0);
            }
            parent.keys[i - 1] = left.removeRightmostKey();//b.把左边兄弟最大的key旋转上去
            return;
        }
        //case 5-2  右边富余,左旋
        if (right != null && right.keyNumber > MIN_KEY_NUMBER) {
            //a.父节点中后继key旋转下来
            x.insertKey(parent.keys[i], x.keyNumber);
            //b.right中最小的孩子换爹
            if (!right.leaf) {
                x.insertChild(right.removeLeftmostChild(), x.keyNumber + 1);//孩子索引比keys索引多1
            }
            //c.right中最小的key旋转上去
            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);
            x.insertKey(parent.removeKey(i), x.keyNumber);
            right.moveToTarget(x);
        }
    }


    private boolean found(Node node, int key, int i) {
        return i < node.keyNumber && node.keys[i] == key;
    }

测试代码: 

import com.itheima.datastructure.btree.BTree;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

class TestBTree {

    @Test
    @DisplayName("split(t=2)")
    void split1() {
        /*
                5               2|5
              /   \     ==>    / | \
           1|2|3   6          1  3  6

         */
        BTree tree = new BTree();
        BTree.Node root = tree.root;
        root.leaf = false;
        root.keys[0] = 5;
        root.keyNumber = 1;

        root.children[0] = new BTree.Node(new int[]{1, 2, 3});
        root.children[0].keyNumber = 3;

        root.children[1] = new BTree.Node(new int[]{6});
        root.children[1].keyNumber = 1;

        tree.split(root.children[0], root, 0);
        assertEquals("[2, 5]", root.toString());
        assertEquals("[1]", root.children[0].toString());
        assertEquals("[3]", root.children[1].toString());
        assertEquals("[6]", root.children[2].toString());
    }

    @Test
    @DisplayName("split(t=3)")
    void split2() {
        /*
                  6                 3|6
               /     \     ==>    /  |  \
           1|2|3|4|5  7         1|2 4|5  7

         */
        BTree tree = new BTree(3);
        BTree.Node root = tree.root;
        root.leaf = false;
        root.keys[0] = 6;
        root.keyNumber = 1;
        root.children[0] = new BTree.Node(new int[]{1, 2, 3, 4, 5});
        root.children[0].keyNumber = 5;

        root.children[1] = new BTree.Node(new int[]{7});
        root.children[1].keyNumber = 1;

        tree.split(root.children[0], root, 0);
        assertEquals("[3, 6]", root.toString());
        assertEquals("[1, 2]", root.children[0].toString());
        assertEquals("[4, 5]", root.children[1].toString());
        assertEquals("[7]", root.children[2].toString());
    }

    @Test
    public void insert() {
        BTree tree = new BTree();
        tree.put(1);
        tree.put(3);
        tree.put(2);
    }

    @Test
    @DisplayName("put(t=2)")
    void testPut1() {
        /*
                                  2
                                 / \
              1|2|3       =>    1   3

                2                2|4
               / \        =>    / | \
              1 3|4|5          1  3  5
                                                    4
                2|4             2|4|6              / \
               / | \      =>   / / \ \     =>     2   6
              1  3 5|6|7      1  3  5 7          / \ / \
                                                1  3 5  7

                4                 4
               / \              /   \
              2   6      =>    2    6|8
             /\  / \          /\   / | \
            1  3 5 7|8|9     1  3 5  7  9

                 4                   4                   4|8
               /   \               /   \                / | \
              2    6|8      =>    2    6|8|10      =>  2  6  10
             /\   / | \          /\   / / \ \         /\  /\  /\
            1  3 5  7 9|10|11   1  3 5 7  9  11      1 3 5 7 9 11
        */
        BTree tree = new BTree();
        tree.put(1);
        tree.put(2);
        tree.put(3);
        tree.put(4);
        tree.put(5);
        tree.put(6);
        tree.put(7);
        tree.put(8);
        tree.put(9);
        tree.put(10);
        tree.put(11);
        assertEquals("[4, 8]", tree.root.toString());
        assertEquals("[2]", tree.root.children[0].toString());
        assertEquals("[6]", tree.root.children[1].toString());
        assertEquals("[10]", tree.root.children[2].toString());
        assertEquals("[1]", tree.root.children[0].children[0].toString());
        assertEquals("[3]", tree.root.children[0].children[1].toString());
        assertEquals("[5]", tree.root.children[1].children[0].toString());
        assertEquals("[7]", tree.root.children[1].children[1].toString());
        assertEquals("[9]", tree.root.children[2].children[0].toString());
        assertEquals("[11]", tree.root.children[2].children[1].toString());

        tree.travel();
    }

    @Test
    @DisplayName("put(t=3)")
    void testPut2() {
        /*
                 3
               /   \
             1|2  4|5|6|7
         */
        BTree tree = new BTree(3);
        tree.put(1);
        tree.put(2);
        tree.put(3);
        tree.put(4);
        tree.put(5);
        tree.put(6);
        tree.put(7);
        assertEquals("[3]", tree.root.toString());
        assertEquals("[1, 2]", tree.root.children[0].toString());
        assertEquals("[4, 5, 6, 7]", tree.root.children[1].toString());
    }

    @Test
    void testSearch() {
        BTree tree = new BTree();
        tree.put(1);
        tree.put(2);
        tree.put(3);
        tree.put(4);
        tree.put(5);
        tree.put(6);
        tree.put(7);
        tree.put(8);
        tree.put(9);
        tree.put(10);
        assertEquals(tree.root, tree.root.get(4));
        assertEquals(tree.root.children[0], tree.root.get(2));
        assertEquals(tree.root.children[0].children[0], tree.root.get(1));
        assertEquals(tree.root.children[0].children[1], tree.root.get(3));
        assertEquals(tree.root.children[1], tree.root.get(6));
        assertEquals(tree.root.children[1], tree.root.get(8));
        assertEquals(tree.root.children[1].children[0], tree.root.get(5));
        assertEquals(tree.root.children[1].children[1], tree.root.get(7));
        assertEquals(tree.root.children[1].children[2], tree.root.get(9));
        assertEquals(tree.root.children[1].children[2], tree.root.get(10));
        assertNull(tree.root.get(11));
    }

    @Test
    void testPut3() {
        BTree tree = new BTree();
        tree.put(6);
        tree.put(3);
        tree.put(8);
        tree.put(1);
        tree.put(2);
        tree.put(5);
        tree.put(4);
        assertEquals("[4]", tree.root.toString());
        assertEquals("[2]", tree.root.children[0].toString());
        assertEquals("[6]", tree.root.children[1].toString());
        assertEquals("[1]", tree.root.children[0].children[0].toString());
        assertEquals("[3]", tree.root.children[0].children[1].toString());
        assertEquals("[5]", tree.root.children[1].children[0].toString());
        assertEquals("[8]", tree.root.children[1].children[1].toString());
    }

    @Test
    @DisplayName("case1: leaf && not found")
    public void testRemove0() {
        /*
             1|2|3|4
         */
        BTree tree = new BTree(3);
        tree.put(1);
        tree.put(2);
        tree.put(3);
        tree.put(4);

        tree.remove(0);
        tree.remove(8);
        assertEquals("[1, 2, 3, 4]", tree.root.toString());
    }

    @Test
    @DisplayName("case3: non-leaf && not found")
    public void testRemove1() {
        /*
                 3
               /   \
             1|2  4|5|6|7
         */
        BTree tree = new BTree(3);
        tree.put(1);
        tree.put(2);
        tree.put(3);
        tree.put(4);
        tree.put(5);
        tree.put(6);
        tree.put(7);

        tree.remove(0);
        tree.remove(8);
        assertEquals("[3]", tree.root.toString());
        assertEquals("[1, 2]", tree.root.children[0].toString());
        assertEquals("[4, 5, 6, 7]", tree.root.children[1].toString());
    }

    @Test
    @DisplayName("case2: remove directly")
    public void testRemove2() {
        /*
                 3
               /   \
             1|2  4|5|6|7
         */
        BTree tree = new BTree(3);
        tree.put(1);
        tree.put(2);
        tree.put(3);
        tree.put(4);

        tree.remove(3);
        assertEquals("[1, 2, 4]", tree.root.toString());
        tree.remove(1);
        assertEquals("[2, 4]", tree.root.toString());
    }

    @Test
    @DisplayName("case4: replace with successor")
    public void testRemove3() {
        /*
                 3
               /   \
             1|2  4|5|6|7
         */
        BTree tree = new BTree(3);
        tree.put(1);
        tree.put(2);
        tree.put(3);
        tree.put(4);
        tree.put(5);
        tree.put(6);
        tree.put(7);

        tree.remove(3);
        assertEquals("[4]", tree.root.toString());
        assertEquals("[1, 2]", tree.root.children[0].toString());
        assertEquals("[5, 6, 7]", tree.root.children[1].toString());
    }

    @Test
    @DisplayName("case5: balance right rotate")
    public void testRemove4() { // 右旋
        /*
                  4
                /   \
             1|2|3  5|6
         */
        BTree tree = new BTree(3);
        tree.put(1);
        tree.put(2);
        tree.put(4);
        tree.put(5);
        tree.put(6);
        tree.put(3);

        tree.remove(5);
        assertEquals("[3]", tree.root.toString());
        assertEquals("[1, 2]", tree.root.children[0].toString());
        assertEquals("[4, 6]", tree.root.children[1].toString());
    }

    @Test
    @DisplayName("case5: balance left rotate")
    public void testRemove5() {
        /*
                  3
                /   \
             1|2   4|5|6

                  4
                /   \
              1|3   5|6
         */
        BTree tree = new BTree(3);
        tree.put(1);
        tree.put(2);
        tree.put(3);
        tree.put(4);
        tree.put(5);
        tree.put(6);

        tree.remove(2);
        assertEquals("[4]", tree.root.toString());
        assertEquals("[1, 3]", tree.root.children[0].toString());
        assertEquals("[5, 6]", tree.root.children[1].toString());
    }

    @Test
    @DisplayName("case5: balance merge a")
    public void testRemove6() { // 合并
        /*
                  3
                /   \
             1|2    4|5
         */
        BTree tree = new BTree(3);
        tree.put(1);
        tree.put(2);
        tree.put(3);
        tree.put(4);
        tree.put(5);

        tree.remove(4);
        assertEquals("[1, 2, 3, 5]", tree.root.toString());
    }

    @Test
    @DisplayName("case5: balance merge b")
    public void testRemove7() { // 合并
        /*
                  3
                /   \
             1|2    4|5
         */
        BTree tree = new BTree(3);
        tree.put(1);
        tree.put(2);
        tree.put(3);
        tree.put(4);
        tree.put(5);

        tree.remove(2);
        assertEquals("[1, 3, 4, 5]", tree.root.toString());
    }

    @Test
    @DisplayName("case6: from right to left")
    void testRemove8() {
        /*

                4|8                    4
               / | \                  / \
              2  6  10        =>     2  6|8
             /\  /\  /\             /\ / | \
            1 3 5 7 9 11           1 3 5 7 9|10


                4                       4
               / \                     / \
              2  6|8        =>        2   6
             /\ / | \                /\  / \
            1 3 5 7  9              1 3 5  7|8


                4                       4
               / \                     / \
              2   6         =>        2   _      =>     2|4
             /\   /\                 /\  /             / | \
            1  3 5  7               1 3 5|6           1 3 5|6

              2|4                       2
             / | \          =>         / \
            1 3  5                    1  3|4

        */
        BTree tree = new BTree();
        tree.put(1);
        tree.put(2);
        tree.put(3);
        tree.put(4);
        tree.put(5);
        tree.put(6);
        tree.put(7);
        tree.put(8);
        tree.put(9);
        tree.put(10);
        tree.put(11);
        tree.remove(11);
        assertEquals("[4]", tree.root.toString());
        assertEquals("[2]", tree.root.children[0].toString());
        assertEquals("[6, 8]", tree.root.children[1].toString());
        assertEquals("[1]", tree.root.children[0].children[0].toString());
        assertEquals("[3]", tree.root.children[0].children[1].toString());
        assertEquals("[5]", tree.root.children[1].children[0].toString());
        assertEquals("[7]", tree.root.children[1].children[1].toString());
        assertEquals("[9, 10]", tree.root.children[1].children[2].toString());
        tree.remove(10);
        assertEquals("[9]", tree.root.children[1].children[2].toString());
        tree.remove(9);
        assertEquals("[6]", tree.root.children[1].toString());
        assertEquals("[5]", tree.root.children[1].children[0].toString());
        assertEquals("[7, 8]", tree.root.children[1].children[1].toString());
        tree.remove(8);
        assertEquals("[7]", tree.root.children[1].children[1].toString());
        tree.remove(7);
        assertEquals("[2, 4]", tree.root.toString());
        assertEquals("[1]", tree.root.children[0].toString());
        assertEquals("[3]", tree.root.children[1].toString());
        assertEquals("[5, 6]", tree.root.children[2].toString());
        tree.remove(6);
        assertEquals("[5]", tree.root.children[2].toString());
        tree.remove(5);
        assertEquals("[2]", tree.root.toString());
        assertEquals("[1]", tree.root.children[0].toString());
        assertEquals("[3, 4]", tree.root.children[1].toString());
        tree.remove(4);
        assertEquals("[3]", tree.root.children[1].toString());
        tree.remove(3);
        assertEquals("[1, 2]", tree.root.toString());
        tree.remove(2);
        assertEquals("[1]", tree.root.toString());
        tree.remove(1);
        assertEquals("[]", tree.root.toString());
    }

    @Test
    @DisplayName("case6: from left to right")
    void testRemove9() {
        /*

                4|8                    4|8                  8
               / | \                  / | \                / \
              2  6  10        =>     _  6  10       =>   4|6  10
             /\  /\  /\             /   /\  /\          / | \  /\
            1 3 5 7 9 11           2|3 5 7 9 11       2|3 5 7 9 11


            remove(2,3) =>  8
                           / \
                          6   10
                         / \  /\
                        4|5 7 9 11

            remove(4,5) =>  8         =>        8|10
                           / \                 / | \
                          _   10             6|7 9 11
                         / \  /\
                        6|7   9 11

            remove(6,7)   =>   10
                               / \
                             8|9 11
        */
        BTree tree = new BTree();
        tree.put(1);
        tree.put(2);
        tree.put(3);
        tree.put(4);
        tree.put(5);
        tree.put(6);
        tree.put(7);
        tree.put(8);
        tree.put(9);
        tree.put(10);
        tree.put(11);
        tree.remove(1);
        assertEquals("[8]", tree.root.toString());
        assertEquals("[4, 6]", tree.root.children[0].toString());
        assertEquals("[10]", tree.root.children[1].toString());
        assertEquals("[2, 3]", tree.root.children[0].children[0].toString());
        assertEquals("[5]", tree.root.children[0].children[1].toString());
        assertEquals("[7]", tree.root.children[0].children[2].toString());
        assertEquals("[9]", tree.root.children[1].children[0].toString());
        assertEquals("[11]", tree.root.children[1].children[1].toString());
        tree.remove(2);
        tree.remove(3);
        assertEquals("[8]", tree.root.toString());
        assertEquals("[6]", tree.root.children[0].toString());
        assertEquals("[10]", tree.root.children[1].toString());
        assertEquals("[4, 5]", tree.root.children[0].children[0].toString());
        assertEquals("[7]", tree.root.children[0].children[1].toString());
        assertEquals("[9]", tree.root.children[1].children[0].toString());
        assertEquals("[11]", tree.root.children[1].children[1].toString());
        tree.remove(4);
        tree.remove(5);
        assertEquals("[8, 10]", tree.root.toString());
        assertEquals("[6, 7]", tree.root.children[0].toString());
        assertEquals("[9]", tree.root.children[1].toString());
        assertEquals("[11]", tree.root.children[2].toString());
        tree.remove(6);
        tree.remove(7);
        assertEquals("[10]", tree.root.toString());
        assertEquals("[8, 9]", tree.root.children[0].toString());
        assertEquals("[11]", tree.root.children[1].toString());
        tree.remove(8);
        tree.remove(9);
        assertEquals("[10, 11]", tree.root.toString());
        assertTrue(tree.root.leaf);
        tree.remove(10);
        tree.remove(11);
        assertEquals("[]", tree.root.toString());
    }

    @Test
    @DisplayName("case6: delete middle")
    void testRemove11() {
        /*

                4|8                5|8               5|8                8
               / | \              / | \             / | \              / \
              2  6  10      =>   2  6  10      =>  2  _  10       => 2|5  10
             /\  /\  /\         /\  /\  /\        /\  |  /\         / | \  /\
            1 3 5 7 9 11       1 3 _ 7 9 11      1 3 6|7 9 11      1 3 6|7 9 11


                 8                    9                 9                5
                / \                /    \            /    \           /    \
              2|5  10     =>     2|5    10     =>  2|5     _    =>   2      9
             / | \  /\          / | \   /\        / | \    |        /\    /  \
            1 3 6|7 9 11       1 3 6|7 _ 11      1 3 6|7 10|11     1 3  6|7 10|11


                  5
               /    \
              2      9     =>
             /\    /  \
            1 3  6|7 10|11

                  6                    7                7
               /    \               /    \           /    \
              2      9     =>      2      9     =>  2     10
             /\    /  \           /\    /  \       /\    /  \
            1 3   7  10|11       1 3   _  10|11   1 3   9   11

                 7                   9                9
               /  \                /  \              / \
              2   10       =>     2   10     =>     2   _      =>   2|9
             /\   / \            /\   / \          /\   |          / | \
            1 3  9  11          1 3  _  11        1 3 10|11       1  3 10|11

               2|9                 3|9                3|10
              / | \         =>    / | \       =>     / | \
             1  3 10|11          1  _ 10|11         1  9  11

               3|10                9|10               10
              / | \         =>    / | \       =>     / \
             1  9  11            1  _  11          1|9  11

               10                   11               9
              / \           =>     / \        =>    / \
            1|9  11              1|9  _            1  11

               9                    11
              / \         =>       / \        =>    1|11
             1  11                1   _
        */
        BTree tree = new BTree();
        tree.put(1);
        tree.put(2);
        tree.put(3);
        tree.put(4);
        tree.put(5);
        tree.put(6);
        tree.put(7);
        tree.put(8);
        tree.put(9);
        tree.put(10);
        tree.put(11);
        tree.remove(4);
        assertEquals("[8]", tree.root.toString());
        assertEquals("[2, 5]", tree.root.children[0].toString());
        assertEquals("[10]", tree.root.children[1].toString());
        assertEquals("[1]", tree.root.children[0].children[0].toString());
        assertEquals("[3]", tree.root.children[0].children[1].toString());
        assertEquals("[6, 7]", tree.root.children[0].children[2].toString());
        assertEquals("[9]", tree.root.children[1].children[0].toString());
        assertEquals("[11]", tree.root.children[1].children[1].toString());
        tree.remove(8);
        assertEquals("[5]", tree.root.toString());
        assertEquals("[2]", tree.root.children[0].toString());
        assertEquals("[9]", tree.root.children[1].toString());
        assertEquals("[1]", tree.root.children[0].children[0].toString());
        assertEquals("[3]", tree.root.children[0].children[1].toString());
        assertEquals("[6, 7]", tree.root.children[1].children[0].toString());
        assertEquals("[10, 11]", tree.root.children[1].children[1].toString());
        tree.remove(5);
        assertEquals("[6]", tree.root.toString());
        assertEquals("[2]", tree.root.children[0].toString());
        assertEquals("[9]", tree.root.children[1].toString());
        assertEquals("[1]", tree.root.children[0].children[0].toString());
        assertEquals("[3]", tree.root.children[0].children[1].toString());
        assertEquals("[7]", tree.root.children[1].children[0].toString());
        assertEquals("[10, 11]", tree.root.children[1].children[1].toString());
        tree.remove(6);
        assertEquals("[7]", tree.root.toString());
        assertEquals("[2]", tree.root.children[0].toString());
        assertEquals("[10]", tree.root.children[1].toString());
        assertEquals("[1]", tree.root.children[0].children[0].toString());
        assertEquals("[3]", tree.root.children[0].children[1].toString());
        assertEquals("[9]", tree.root.children[1].children[0].toString());
        assertEquals("[11]", tree.root.children[1].children[1].toString());
        tree.remove(7);
        assertEquals("[2, 9]", tree.root.toString());
        assertEquals("[1]", tree.root.children[0].toString());
        assertEquals("[3]", tree.root.children[1].toString());
        assertEquals("[10, 11]", tree.root.children[2].toString());
        tree.remove(2);
        assertEquals("[3, 10]", tree.root.toString());
        assertEquals("[1]", tree.root.children[0].toString());
        assertEquals("[9]", tree.root.children[1].toString());
        assertEquals("[11]", tree.root.children[2].toString());
        tree.remove(3);
        assertEquals("[10]", tree.root.toString());
        assertEquals("[1, 9]", tree.root.children[0].toString());
        assertEquals("[11]", tree.root.children[1].toString());
        tree.remove(10);
        assertEquals("[9]", tree.root.toString());
        assertEquals("[1]", tree.root.children[0].toString());
        assertEquals("[11]", tree.root.children[1].toString());
        tree.remove(9);
        assertEquals("[1, 11]", tree.root.toString());
    }

}

  • 6
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ray-国

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值