6.9 B+树

  B+树我花了很久时间才实现。具体说来难处在于资料都讲得不详细,跳过了很多细节。所以我很久没更新博文了。B+树不仅仅是对B-树的扩展。B+树在B-树的基础上,增加了以下规则:
  1. 数据只保存在叶子上;
  2. 所有的叶子通过连接起来,形成链表。
  3. 叶子和中间节点保存的数据数量是不一致的。中间节点保存的key数目是m-1,叶子节点保存的数据是l,l和m是毫不相干的变量。因为每个节点对应磁盘的一个单元,只存索引的中间节点和存数据的叶子节点数据块长度肯定不一样。所以m值和索引的长度相关,l值和数据的长度相关。毫无疑问,l值大部分情况下都会小于m值。
  4. 因为数据只保存在叶子上,所以key和叶子上的数据一定会有重复。重复的条件是右边的孩子可以等于索引键值。
  一棵典型的B-树如下图:
在这里插入图片描述
  需要注意的是实际项目不会直接使用B+树,而是使用B+树的三种变体:B-link树,B*树和sx-latch B+树。但是都是以B+树为基础的。B+树的算法和B-树最大的不同是B+树无论删除还是插入,都是先从root查找到叶子,操作数据时从叶子开始,向上递归,而B-树是从root开始向下递归,变查找边操作数据。

插入过程

  B+树的插入过程还是比较简单的。但是和B-树不同。找到叶子,如果叶子没满,就直接插入数据,如果叶子满了。那就先创建一个新节点,然后把50%的数据移动到新叶子,当然B-树的分裂也是移动50%的数据,这一点很相似。B+树的中间节点只能左移右移,不能进行迁移50%数据这样的操作。但是不同的是递归方向,另外的不同点是因为B+树所有数据存在叶子上,所以新分裂出来的索引,是右边子树的最小值。
  我给个表格:

场景处理方法
叶子未满直接插入
叶子溢出创建新叶子,放在右边,将原叶子一半的数据传输过来,插入父节点中,key插入新节点中, 右边叶子的第一个元素作为父key(这个操作会导致下面的两种溢出)
中间节点溢出创建空节点,把左边最大的子元素(假设为max-child)移动到新节点,key放到新节点中。向下找到max-child的最小元素,插入到父节点
root溢出和中间节点一样,不同的是将max-child的最小元素作为新root的唯一元素,并且链接好原root和新节点

  给几张图吧,首先是叶子的分裂50%的图。
在这里插入图片描述
在这里插入图片描述

  下面这张图是向上递归分裂的过程。前面说了叶子节点迁移50%,中间节点只能分出一个key,如果是root则设置新的root,增加树的高度,下面这两张图,把三种场景都囊括了。
在这里插入图片描述
在这里插入图片描述

删除过程

  删除过程是非常复杂的。我也是卡在这里。我卡住的原因是按照B-树的思想,从上往下处理,结果进入了一个死局,改好了这里,坏了那里。后来详细阅读各个资料,茶饭不思,终于搞明白了删除的正确方法,删除的正确方法是,先定位到叶子,在叶子上删除。
  我的代码为了简化,是l=m-1的。但是思想一样,删除过程具体的算法是这样的:
  1. 定位到叶子;
  2. 如果叶子就是root,直接删除,返回,结束。
  3. 如果叶子没有下溢(元素数量等于 ⌈ l 2 ⌉ − 1 \lceil \frac l2\rceil-1 2l1,删除后元素数量不满足B+树条件,式子中的符号是向上取整的意思)并且要删除的不是叶子的最小元素,那么直接删除,返回,结束。
  4. 如果没有下溢,但是key是叶子的最小元素,那么先删除自己,再向上循环用自己的兄弟替换和自己相同的元素。
  5. 如果下溢,则向左借一个元素、向右借一个元素或者合并叶子。
  给几张图,向左借叶子元素,需要注意的是需要换掉父节点元素:
在这里插入图片描述
在这里插入图片描述
  向右借叶子元素:
在这里插入图片描述
在这里插入图片描述
  合并叶子,这个时候会删除父节点索引元素,与B-树不同的是,B+树会存在父元素与子元素相同的情况,这种情况下合并后不是满的:
在这里插入图片描述
在这里插入图片描述
  在合并叶子后,会删除父节点元素。删除非叶子元素时,是先判断是否会下溢,如果会,那么先调整自己,再删除。父元素的左移右移和合并代码类似。我就不再给图了。
  合并后如果会造成root只有一个子节点,那么直接将root指向这个唯一的子节点,因为原root没有保存数据,只是保存了索引,所以可以直接丢弃。

java代码

  我这里的Java代码并不全,篇幅有限,没有贴父类代码。具体可以从我的git中下载,地址为:https://e.coding.net/buildt/data-structure/trees.git 。
  BPlusTree的代码如下:

package com.youngthing.trees.bplus;

import com.youngthing.trees.b.BTree;
import com.youngthing.trees.b.SortedMultiwayNode;

import java.util.logging.Logger;

public class BPlusTree<T extends Comparable<T>> extends BTree<T> {

    private final BPlusNode head;
    private final BPlusNode tail;
    private final int m;
    Logger bplusLogger = Logger.getLogger("bplus.split");

    public BPlusTree(int m) {
        super(m);
        super.root = new BPlusNode<>(m);
        head = tail = (BPlusNode) root;
        this.m = m;
    }

    @Override
    public void add(T t) {
        BPlusNode<T> r = (BPlusNode<T>) this.root;
        if (r.getValue().isEmpty()) {
            r.insert(t);
        } else {
            BPlusNode<T> leaf = (BPlusNode<T>) ((SortedMultiwayNode<T>) root).findToInsert(t);
            insert(t, leaf);
        }
    }


    @Override
    public BPlusNode<T> getRoot() {
        return (BPlusNode<T>) super.getRoot();
    }

    @Override
    public void delete(T key) {
        if (root.isLeaf()) {
            final BPlusNode<T> node = (BPlusNode<T>) root;
            node.removeKey(node.binarySearch(key)[0]);
            return;
        }
        // 第一步是定位到叶子
        final BPlusNode<T> leafNode = (BPlusNode<T>) getRoot().findLeafNode(key);
        // 满足条件就直接删除
        int t = m / 2;
        final int[] ints = leafNode.binarySearch(key);
        // 节点最小为t-1
        if (leafNode.length >= t && !leafNode.values[0].equals(key)) {
            // 直接删除
            leafNode.removeKeyWithoutBranches(ints[0]);
        } else {
            // 删除自己
            // 再删除父节点
            leafNode.removeLeaf(t, ints);
        }
        if (root.length == 0 && !root.isLeaf()) {
            root = root.branches(0);
            root.setParent(null);
        }
    }


    private static class InsertResult<X extends Comparable<X>> {
        private final int index;
        private final BPlusNode<X> node;

        public InsertResult(int index, BPlusNode<X> node) {
            this.index = index;
            this.node = node;
        }

    }

    private InsertResult<T> insert(T t, BPlusNode<T> node) {
        if (node.isFull()) {
            return splitAndInsert(t, node);
        } else {
            return new InsertResult(node.insert(t), node);
        }
    }

    private InsertResult splitAndInsert(T t, BPlusNode<T> node) {
        bplusLogger.info("before split" + node.hashCode());
        bplusLogger.info(root.toGraphString());
        // 那就分裂啊
        final BPlusNode<T> newNode = new BPlusNode<>(m);
        // transer
        if (node.isLeaf()) {
            node.transfer(newNode);
        } else {
            newNode.initBranches();
            newNode.branches(0, node.branches(m - 1));
            node.length--;
        }
        final InsertResult<T> insertResult = insert(t, newNode);
        final int index = insertResult.index;
        if (node.isLeaf()) {
            node.setNext(newNode);
        }
        if (node == this.root) {
            final BPlusNode<T> newRoot = new BPlusNode<>(m);
            // 这里加个判断
            // 这里有个bug
            if (this.root.isLeaf()) {
                insert((T) newNode.values[0], newRoot);
            } else {
                insert((T) this.root.values[root.values.length - 1], newRoot);
            }
            newRoot.initBranches();
            newRoot.branches(0, this.root);
            newRoot.branches(1, newNode);
            this.root = newRoot;
        } else {
            // 这里错了
            BPlusNode<T> min = newNode;
            while (!min.isLeaf()) {
                min = (BPlusNode<T>) min.branches(0);
            }
            final InsertResult<T> insert = insert(min.getValue().get(0), (BPlusNode<T>) node.getParent());
            // 插入之后还要右移所有的key啊
            // 如果parent分裂之后,这个就不对了
            insert.node.initBranches();
            insert.node.branches(insert.index + 1, newNode);
        }
        bplusLogger.info("after split" + node.hashCode());
        bplusLogger.info(root.toGraphString());
        return new InsertResult(index, newNode);
    }
}

  BPlusNode类代码如下:

package com.youngthing.trees.bplus;

import com.youngthing.trees.b.BNode;
import com.youngthing.trees.b.MultiwayNode;
import com.youngthing.trees.b.SortedMultiwayNode;

public class BPlusNode<T extends Comparable<T>> extends BNode<T> {

    private BPlusNode<T> prev;
    private BPlusNode<T> next;

    public BPlusNode(int m) {
        super(m);
    }

    @Override
    public void add(T key) {
        SortedMultiwayNode<T> leaf = (SortedMultiwayNode<T>) super.findToInsert(key);
        if (leaf.isFull()) {
            // 那就分裂啊
            ((BNode<T>) leaf).split();
        } else {
            leaf.insert(key);
        }

    }

    @Override
    public int[] binarySearch(T key) {
        // 其实对于二分法做一个修改
        int from = 0;
        int to = length - 1;
        while (to - from > 1) {
            int index = (from + to) / 2;
            final int c = key.compareTo((T) values[index]);
            if (c > 0) {
                from = index;
            } else if (c < 0) {
                to = index;
            } else {
                return new int[]{index};
            }
        }
        final int f = key.compareTo((T) values[from]);
        if (f == 0) {
            return new int[]{from};
        } else if (f < 0) {
            return new int[]{-1, from};
        }

        final int t = key.compareTo((T) values[to]);
        if (t == 0) {
            return new int[]{to};
        } else if (t < 0) {
            return new int[]{from, to};
        }
        return new int[]{to, to + 1};
    }

    public void transfer(BPlusNode<T> newNode) {
        int t = m / 2;
        for (int i = t; i < length; i++) {
            newNode.values[i - t] = this.values[i];
        }
        if (this.isNotLeaf()) {
            newNode.initBranches();
        }
        for (int i = t; i <= length; i++) {
            newNode.branches(i - t, branches(i));
        }
        newNode.length = this.length - t;
        // 减少leaf的length
        this.length = t;
    }

    public <T extends Comparable<T>> void setNext(BPlusNode<T> newNode) {
        this.next = (BPlusNode) newNode;
        if (newNode != null) {
            newNode.prev = (BPlusNode) this;
        }
    }

    @Override
    protected boolean parentToChild(StringBuilder sb) {
        super.parentToChild(sb);
        if (this.next != null) {
            sb.append("  \"").append(this.getGraphId()).append("\"");
            sb.append(" -> ");
            sb.append("\"").append(this.next.getGraphId()).append("\"\n");
        }
        return true;
    }

    @Override
    public void removeKey(int index) {
        removeKeyReturn(index);
    }

    /**
     * 带返回值
     *
     * @param index
     */
    public boolean removeKeyReturn(int index) {
        if (isNotLeaf() && values[index].equals(branches(index + 1).values[0])) {
            return removeInternalKey(index);
        } else {
            super.removeKey(index);
            return false;
        }
    }

    /**
     * 寻找所在的节点
     *
     * @param t
     * @return
     */
    public MultiwayNode<T> findLeafNode(T t) {

        // 二分查找
        SortedMultiwayNode<T> x = this;
        while (!x.isLeaf()) {
            final int[] ints = x.binarySearch(t);
            if (ints.length == 1) {
                x = (SortedMultiwayNode<T>) x.branches(ints[0] + 1);
            } else {
                x = (SortedMultiwayNode<T>) x.branches(ints[ints.length - 1]);
            }
        }
        return x;
    }

    /**
     * @param index
     * @return true 直接删除了子节点
     */
    private boolean removeInternalKey(int index) {
        // n >=自己的最小值
        BPlusNode<T> n = (BPlusNode<T>) branches(index + 1);
        while (n.isNotLeaf()) {
            n = (BPlusNode<T>) n.branches(0);
        }
        final Object replaceValue = n.values[0];

        if (!values[index].equals(replaceValue)) {
            values[index] = replaceValue;
            n.removeLeaf(m / 2, new int[]{0});
        } else {
            // 这一行有bug 改好了
            if (n.length > 1) {
                values[index] = n.values[1];
            } else {
                super.removeKeyWithoutBranches(index);
                // 因为右边相等,所以直接去除
                final BPlusNode<T> leaf = (BPlusNode<T>) this.branches(index + 1);
                leaf.prev.setNext(getNext());
                // 直接去除的话,叶子的链表也要维护
                removeBranch(index + 1);

                return true;
            }
            // 手动处理,要两个一起删除,还是有bug
            // 可以得知,这一定是叶子。
        }
        return false;
    }

    public BPlusNode<T> getNext() {
        return next;
    }

    public BPlusNode<T> getPrev() {
        return prev;
    }

    public void borrowRight() {
        // 借一个啊
        if (next.values[getIndexInParent()].equals(this.getParent().values[getIndexInParent()])) {
            // 这里有一个bug
            this.getParent().values[getIndexInParent()] = next.values[getIndexInParent() + 1];
        }
        this.values[this.length] = next.values[0];
        next.removeKey(0);
        this.length++;
    }

    public void borrowLeft() {
        // 借一个啊
        this.insert((T) prev.values[prev.length - 1]);
        prev.removeKey(prev.length - 1);
        // parent要改成
        getParent().values[prev.getIndexInParent()] = this.values[0];
    }


    private void lendFromRight() {
        // 第一,先改parent
        final BNode<T> p = getParent();
        final MultiwayNode<T> sibling = p.branches(getIndexInParent() + 1);
        final Object pvalue = p.values[getIndexInParent()];

        // 第一步 先设置好左边
        // left
        this.values[length++] = pvalue;
        this.branches(length, sibling.branches(0));
        // parent, 这里是右边最小的
        BPlusNode<T> min = (BPlusNode<T>) sibling.branches(getIndexInParent() + 1);
        while (min.isNotLeaf()) {
            min = (BPlusNode<T>) min.branches(0);
        }
        p.values[getIndexInParent()] = min.values[0];

        // right
        sibling.removeKeyWithoutBranches(0);
        sibling.removeBranch(0);
        // 再进行一个判断
        final BPlusNode<T> x = (BPlusNode<T>) sibling.branches(0);
        if (x.values[0].equals(pvalue)) {
            if (x.isLeaf()) {
                x.removeLeaf(m / 2, new int[]{0});
            } else {
                throw new RuntimeException("还没实现啊");
            }
        }
    }

    private void lendFromLeft() {
        // 第一,先改parent
        final BNode<T> p = getParent();
        final MultiwayNode<T> left = p.branches(getIndexInParent() - 1);
        final Object pvalue = p.values[getIndexInParent() - 1];
        p.values[getIndexInParent() - 1] = left.values[left.length - 1];
        // left
        left.directRemoveKey(left.length - 1);
        // right
        this.insert((T) pvalue);
        // 所有的向右移动
        for (int i = this.length; i >= 0; i--) {
            this.branches(i + 1, this.branches(i));
        }
        this.branches(0, left.branches(left.length + 1));
    }

    public void removeLeaf(int t, int[] ints) {
        if (this.length >= t) {
            T key = (T) this.values[ints[ints.length - 1]];
            T next = (T) this.values[ints[ints.length - 1] + 1];
            BPlusNode<T> x = (BPlusNode<T>) getParent();
            while (x != null) {
                final int[] indices = x.binarySearch(key);
                if (indices.length == 1) {
                    x.values[indices[0]] = next;
                }
                x = (BPlusNode<T>) x.getParent();
            }
            this.removeKeyWithoutBranches(ints[ints.length - 1]);
        } else {
            // 这种场景需要借一个
            final BPlusNode<T> next = this.getNext();
            final BPlusNode<T> prev = this.getPrev();
            final BPlusNode<T> parent = (BPlusNode<T>) getParent();
            if (next != null && next.getParent() == parent && next.length >= t) {
                System.out.println("before borrowRight");
                System.out.println(getRoot().toGraphString());
                this.borrowRight();

                System.out.println("after borrowRight");
                System.out.println(getRoot().toGraphString());
                this.removeKey(ints[ints.length - 1]);
            } else if (prev != null && prev.getParent() == parent && prev.length >= t) {
                System.out.println("before borrowLeft");
                System.out.println(getRoot().toGraphString());
                this.borrowLeft();
                System.out.println("after borrowLeft");
                System.out.println(getRoot().toGraphString());
                this.removeKey(ints[ints.length - 1] + 1);
            } else {
                // merge会减少parent数量
                // 所以需要判断
                if (!parent.isRoot() && parent.length < t) {
                    parent.adjust();
                    System.out.println("after parent adjust:" + parent);
                    System.out.println(getRoot().toGraphString());
                }
                if (next != null && next.getParent() == parent) {
                    System.out.println("before merge");
                    System.out.println(getRoot().toGraphString());
                    this.merge();
                    System.out.println("after merge");
                    System.out.println(getRoot().toGraphString());
                    this.removeKey(ints[ints.length - 1]);
                } else {

                    System.out.println("before merge");
                    System.out.println(getRoot().toGraphString());
                    this.merge();

                    System.out.println("after merge");
                    System.out.println(getRoot().toGraphString());
                    prev.removeKey(ints[ints.length - 1] + prev.length - this.length);
                }

            }

        }
    }

    private void adjust() {
        int t = m / 2;
        final BNode<T> p = getParent();
        // 场景分类一定要搞清楚
        if (this.getIndexInParent() != p.length && p.branches(getIndexInParent() + 1).length >= t) {
            // 从右边借
            this.lendFromRight();
        } else if (this.getIndexInParent() != 0 && p.branches(getIndexInParent() - 1).length >= t) {
            this.lendFromLeft();
        } else {
            System.out.println("第三种场景");

            BPlusNode<T> left;
            BPlusNode<T> right;
            if (this.getIndexInParent() != p.length) {
                left = this;
                right = (BPlusNode<T>) p.branches(getIndexInParent() + 1);
            } else {
                left = (BPlusNode<T>) p.branches(getIndexInParent() - 1);
                right = this;
            }
            BPlusNode<T> parent = (BPlusNode<T>) getParent();

            merge(left, right, parent);
        }
    }

    private void merge(BPlusNode<T> left, BPlusNode<T> right, BPlusNode<T> parent) {
        T middleValue;
        if (parent.values[left.getIndexInParent()].equals(right.values[0])) {
            // right 的最小值
            BPlusNode<T> x = right;
            while (x.isNotLeaf()) {
                x = (BPlusNode<T>) right.branches(0);
            }
            middleValue = (T) x.values[0];
        } else {
            middleValue = (T) parent.values[left.getIndexInParent()];
        }
        left.values[left.length++] = middleValue;
        left.branches(left.length, right.branches(0));
        for (int i = 0; i < right.length; i++) {
            left.values[left.length++] = right.values[i];
            left.branches(left.length, right.branches(i + 1));
        }
        // right没了
        // 处理parent
        parent.removeKeyWithoutBranches(left.getIndexInParent());
        parent.removeBranch(left.getIndexInParent() + 1);
    }

    public void merge() {
        BPlusNode<T> left;
        BPlusNode<T> right;
        if (next != null && next.getParent() == getParent()) {
            // 合并右边
            left = this;
            right = next;
        } else if (prev != null && prev.getParent() == getParent()) {
            // 合并左边
            left = prev;
            right = this;
        } else {
            // 因为B+树至少有2个孩子,所以不可能出现这种场景
            throw new RuntimeException("不可能出现这种场景");
        }
        left.merge(right);
        int iip = left.getIndexInParent();
        // 删除parent
        getParent().removeKeyWithoutBranches(iip);
        getParent().removeBranch(iip + 1);
        getParent().branches(iip, left);
    }


    private void merge(BPlusNode<T> right) {
        this.setNext(right.next);
        final Object p = this.getParent().values[getIndexInParent()];
        if (!p.equals(right.values[0])) {
            this.insert((T) p);
        }
        for (int i = 0; i < right.length; i++) {
            this.insert((T) right.values[i]);
        }

    }

    @Override
    public int insert(T key) {
        if (isFull()) {
            throw new RuntimeException();
        }
        final int index = nextIndex(key);
        // 要全部移动啊
        // 比如 2 5 , insert 3 -> 2 3 5
        if (!isLeaf() && index < length) {
            branches(length + 1, branches(length));
        }
        for (int i = length; i > index; i--) {
            values[i] = values[i - 1];
        }
        if (!isLeaf() && index < length) {
            for (int i = Math.min(length + 2, m - 1); i - 1 > index; i--) {
                branches(i, branches(i - 1));
            }
        }
        values[index] = key;
        super.length++;
        return index;
    }

    @Override
    protected void ports(StringBuilder sb) {
        if (isLeaf()) {
            return;
        }
        super.ports(sb);
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

醒过来摸鱼

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

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

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

打赏作者

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

抵扣说明:

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

余额充值