B-树(插入删除)代码+逻辑

1.出现缘由

当数据量很大时,内存不够,找磁盘。为了提高取数据的速度,减少树的高度->多叉

2.特点

  • 每个结点包含多个关键字,包含上界和下界。最小度数(取决于磁盘块)做衡量t>=2
  • 所有的叶子结点都在同一层,并且不带信息 (可以看做是外部结点或查找失败的结 点,实际上这些结点不存在,指向这些结点的指针为空)
  • 对于一个有x个关键字的结点,它有x+1个孩子
  • 每个结点中的关键字升序
  • 根结点至少包含一个关键字,但是上界是一样的

3.插入

主动插入,往叶子结点插

在插入寻找的过程中,只要碰到满结点都做拆分,不管他是不是目标结点

4.删除

4.1如果待删除的结点在结点x中 且x是叶子结点直接删除

4.2如果待删除的结点在结点x中 且x是内部结点。

4.2.1如果位于x中某个关键字前一个孩子y至少有t个关键字

删除关键字G,其前一个孩子为DEF,关键字G的直接前驱为F,删除F,然后将G替换为F

4.2.2如果位于x中某个关键字前一个孩子y少于t个关键字,后一个孩子不少于t个关键字

删除关键字C,其前一个孩子为AB,小于t=3,后一个孩子DEF不小于3,关键字C的直接后继为D,删除D,然后将C替换为D

4.2.3如果位于x中某个关键字前、后孩子都少于t个关键字

删除关键字C,前一个孩子为AB,后一个孩子为DE(此处假设F已经被删掉了),均小于t=3,合并关键字C和结点DE到结点AB中,此时C在叶子结点中,直接删除

4.3如果打破了b树规则(删除是递归过程,删除和调整是一体的)

4.3.1如果子树和他相邻的兄弟都只有t-1,子树和兄弟合并,父结点一个关键字下移

4.3.2如果只有子树是t-1相邻的兄弟至少t个,则将x的一个关键字下移到子树中,将相邻的兄弟中的一个关键字上移到x中,相应的孩子指针也上移,使得x新增加一个关键字(直接返回不需要递归)

package Tree.bTree;

import java.util.LinkedList;

public class BTreeNode {
    //b树的阶
    int M;
    //关键字列表
    LinkedList<Integer> values;
    //父结点
    BTreeNode parent;
    //孩子列表
    LinkedList<BTreeNode> children;
    //构造构造一棵空的B-树
    private BTreeNode(){
        this.values = new LinkedList<Integer>();
        this.children = new LinkedList<BTreeNode>();
    }
    //构造一棵空的m阶B-树
    public BTreeNode(int m){
        this();
        if(m<3){
            throw new RuntimeException("阶数大于2");
        }
        this.M = m;
    }
    //根据父结点构造一个空的孩子结点
    public BTreeNode(BTreeNode parent){
        this(parent.M);
        this.parent = parent;
    }

    /**
     * 获取根结点
     * @return 根结点
     */
    public BTreeNode getRoot(){
        BTreeNode p = this;
        while(!p.isRoot()){
            p = p.parent;
        }
        return p;
    }

    /**
     * 判断当前结点是不是根结点
     * @return 是就返回真
     */
    public boolean isRoot(){
        return parent == null;
    }

    /**
     * 判断当前结点是不是空结点
     * @return 是就真
     */
    public boolean isEmpty(){
        if(values == null||values.size()==0){
            return true;
        }
        return false;
    }

    /**
     * 清空当前结点,保留父关系
     */
    public void clear(){
        values.clear();
        children.clear();
    }

    /**
     * 从当前结点往下查找目标值target
     * @param target 目标值
     * @return 找到返回找到的结点,没有就返回null的叶子结点
     */
    public BTreeNode search(int target){
        if(isEmpty()){
            return this;
        }
        int valueIndex = 0;
        while(valueIndex < values.size()&&values.get(valueIndex)<=target){
            if(values.get(valueIndex) == target){
                return this;
            }
            valueIndex++;
        }
        return children.get(valueIndex).search(target);
    }

    /**
     *插入结点,先找到根结点,从根结点往下找到插入的位置,如果是重复的数据就抛出异常
     * 插入可能会产生新的根结点,返回新的结点
     * @param e 插入的关键字
     * @return 新的根结点
     */
    public BTreeNode insert(int e){
        if(isEmpty()){
            values.add(e);
            children.add(new BTreeNode(this));
            children.add(new BTreeNode(this));
            return this;
        }
        BTreeNode p = getRoot().search(e);
        if(!p.isEmpty()){
            throw new RuntimeException("cannot insert duplicate key to B-Tree, key: " + e);
        }
        insertNode(p.parent,e,new BTreeNode(p.M));
        return getRoot();
    }

    /**
     * 向指定结点内插入关键字和关键字右侧的孩子结点,如果没有大于2m-1,就找到结点内部的插入位置,然后直接插入
     * 一定是叶子插入
     * @param node 插入的结点
     * @param e 待插入的关键字
     * @param eNode 待插入的关键字右侧的孩子结点
     */
    private void insertNode(BTreeNode node,int e,BTreeNode eNode){
        int valueIndex = 0;
        while(valueIndex < node.values.size()&&node.values.get(valueIndex)<e){
            valueIndex++;
        }
        node.values.add(valueIndex,e);
        eNode.parent = node;
        node.children.add(valueIndex+1,eNode);
        //这里的M阶相当于2m-1
        if(node.values.size()>M-1){
            int upIndex = M/2;
            int up = node.values.get(upIndex);
            //当前结点分为左右两部分,左边的parent不变,新建右边结点瓜分原来的右半边
            BTreeNode rNode = new BTreeNode(M);
            rNode.values = new LinkedList<>(node.values.subList(upIndex+1,M));
            rNode.children = new LinkedList<>(node.children.subList(upIndex+1,M+1));
            for (BTreeNode rChild : rNode.children){
                rChild.parent = rNode;
            }
            node.values = new LinkedList<>(node.values.subList(0,upIndex));
            node.children = new LinkedList<>(node.children.subList(0,upIndex+1));
            //从根结点上升,选取上升关键字作为新的根结点
            if(node.parent == null){
                node.parent = new BTreeNode(M);
                node.parent.values.add(up);
                node.parent.children.add(node);
                node.parent.children.add(rNode);
                rNode.parent = node.parent;
                return;
            }
            //父结点增加关键字,递归调用
            insertNode(node.parent,up,rNode);

        }
    }

    /**
     * 以当前节点为根,在控制台打印B-树
     */
    public void print() {
        printNode(this, 0);
    }

    /**
     * 控制台打印节点的递归调用
     *
     * @param node 要打印节点
     * @param depth 递归深度
     */
    private void printNode(BTreeNode node, int depth) {
        StringBuilder sb = new StringBuilder();
        for(int i = 1; i < depth; i++) {
            sb.append("|    ");
        }
        if(depth > 0) {
            sb.append("|----");
        }
        sb.append(node.values);
        System.out.println(sb.toString());
        for(BTreeNode child : node.children) {
            printNode(child, depth+1);
        }
    }

    public BTreeNode delete(int e){
        if(isEmpty()){
            return this;
        }
        BTreeNode p = getRoot().search(e);
        if(p.isEmpty()) {
            throw new RuntimeException("the key to be deleted is not exist, key: " + e);
        }
        //找到目标结点的位置
        int valueIndex = 0;
        while(valueIndex < p.values.size() && p.values.get(valueIndex) < e){
            valueIndex++;
        }
        //不是最下层结点中的关键字
        if(!p.children.get(0).isEmpty()){
            BTreeNode rMin = p.children.get(valueIndex);
            while(!rMin.children.get(0).isEmpty()){
                //一直找到叶子结点
                rMin = rMin.children.get(0);
            }
            //用最小的最大关键字替换
            p.values.set(valueIndex,rMin.values.get(0));
            //删除了最下层结点有可能导致关键字数小于t-1
            //rMin是最下层结点,valueIndex是原结点的位置
            return delete(rMin,valueIndex,0);
        }
        return delete(p,valueIndex,0);
    }

    /**
     * 删除指定结点中的关键字和孩子,并处理删除后打破b-树规则的情况
     * @param target 目标结点
     * @param valueIndex 关键字索引
     * @param childIndex 孩子索引
     * @return 删除完成后的根结点
     */
    private BTreeNode delete(BTreeNode target,int valueIndex,int childIndex){
        target.values.remove(valueIndex);
        target.children.remove(childIndex);
        //目标结点满足规则直接返回,不进行下一步调整
        if(target.children.size() >= Math.ceil(M/2.0)){
            return target.getRoot();
        }
        //根结点比较特殊
        if(target.isRoot()){
            //孩子数量满足要求
            if(target.children.size()>1){
                return target;
            } else{
                //只有一个孩子,直接上移,返回
                BTreeNode newRoot = target.children.get(0);
                newRoot.parent = null;
                return newRoot;
            }
        }
        //找到目标结点的位置
        int parentChildIndex = 0;
        while(parentChildIndex < target.parent.children.size() && target.parent.children.get(parentChildIndex) != target) {
            parentChildIndex++;
        }
        /**
         * 等于0表示没有左兄弟
         * 不满足b树结构要求,下面就是进行调整步骤
         */
        if(parentChildIndex > 0 &&target.parent.children.get(parentChildIndex - 1).values.size() >= Math.ceil(M/2.0)){
            //左兄弟关键字数大于t-1
            int downKey = target.parent.values.get(parentChildIndex-1);
            BTreeNode leftSibling = target.parent.children.get(parentChildIndex-1);
            int upKey = leftSibling.values.remove(leftSibling.values.size()-1);
            //删除孩子用来替换的关键字
            BTreeNode mergeChild = leftSibling.children.remove(leftSibling.children.size()-1);
            //关键字下移
            target.values.add(0, downKey);
            //孩子指针也上移
            target.children.add(0, mergeChild);
            //替换
            target.parent.values.set(parentChildIndex-1, upKey);
            return target.getRoot();
        } else if(parentChildIndex < target.parent.children.size()-1 &&
                target.parent.children.get(parentChildIndex+1).values.size() >= Math.ceil(M/2.0)){
            // 右兄弟关键字数大于 ceil(M/2)-1,且有右孩子
            int downKey = target.parent.values.get(parentChildIndex);
            BTreeNode rightSibling = target.parent.children.get(parentChildIndex+1);
            int upKey = rightSibling.values.remove(0);
            BTreeNode mergeChild = rightSibling.children.remove(0);
            target.values.add(downKey);
            target.children.add(mergeChild);
            target.parent.values.set(parentChildIndex, upKey);
            return target.getRoot();
        } else{
            //左右兄弟关键字数都不大于t-1
            int parentValueIndex;
            if(parentChildIndex > 0){
                //如果有左兄弟,和左兄弟合并
                BTreeNode leftSibling = target.parent.children.get(parentChildIndex -1);
                //加上父结点的关键字
                parentValueIndex = parentChildIndex - 1;
                int downKey = target.parent.values.get(parentValueIndex);
                leftSibling.values.add(downKey);
                //加上目标结点的剩余信息
                leftSibling.values.addAll(target.values);
                target.children.forEach(c -> c.parent = leftSibling);
                leftSibling.children.addAll(target.children);
            } else{
                //没有左兄弟,和右兄弟合并
                BTreeNode rightSibling = target.parent.children.get(parentChildIndex + 1);
                //加上父结点关键字
                parentValueIndex = parentChildIndex;
                int downKey = target.parent.values.get(parentValueIndex);
                rightSibling.values.add(0,downKey);
                //加上目标结点的剩余信息
                rightSibling.values.addAll(0, target.values);
                target.children.forEach(c -> c.parent = rightSibling);
                rightSibling.children.addAll(0,target.children);
            }
            //递归删除父结点关键字和孩子
            return delete(target.parent,parentValueIndex,parentChildIndex);
        }
    }
}

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值