B树与B+树(结尾附源码地址)

B树原理及部分方法展示

节点分裂

传入父节点与子节点以及子节点在父节点中的对应位置,若为根节点,则在调用分裂方法前创建新的根节点作为原节点的父节点;新建右子节点,原节点作为左子节点,将左子节点数据中间值放入父节点,右侧值遍历赋给右子节点,若其下存在子节点,则一同过户,删除左子节点中给了父节点与右节点的数据,将右节点挂在父节点下。

	//节点存储达到上限,它裂开了
    public void splitNode(Node<K,V> root,Node<K,V> child ,int index){
        //自我分裂出的新宝宝
        Node<K,V> newBaby=new Node<>(this.kComparator);
        //原分裂节点若为叶子节点或非叶子节点,新宝宝与之相同
        newBaby.setLeaf(child.isLeaf());
        //将后半部分养大的孩子扔给新节点 中间值下标为t-1
        for(int i=t;i<child.getSize();i++){
            newBaby.addEntry(child.getEntrys().get(i));
        }
        //将原分裂节点的中间元素拿出来准备扔给父母级节点
        Entry<K,V> midEntry=child.getEntrys().get(t-1);
        //将原分裂节点的中间entry及右边全部删除
        for(int i=maxSiez-1;i>=t-1;i--){
            child.getEntrys().remove(i);
        }
        //如果分裂的节点不是叶子节点,则原分裂节点的孩子们要去找新的父母
        if( ! child.isLeaf()){
            //中间节点右边子节点下标为t,将t及之后的子节点塞给新节点
            for(int i=t;i<maxSiez+1;i++){
                newBaby.getChildren().add(child.getChildren().get(i));
            }
            //将塞过的子节点删除,从后向前删否则+1会导致节点漏删
            for(int i=maxSiez;i>=t;i--){
                child.getChildren().remove(i);
            }
        }
        //将中间元素加入原分裂节点的父节点
        addEntry(root,midEntry);
        //将分裂出的新节点挂在原分裂节点的父节点,原分裂节点所在下标为index,所以新增节点下标为index+1
        root.getChildren().add(index+1,newBaby);
    }

查询

使用查询方法,查出数据应该在当前节点的下标。

	//二分查找所要插入的entry或所在子节点所在的下标
    public Result<V> search(K key){
        int begin=0;
        int end=this.getSize()-1;
        if(end<0){
            return new Result<V>(false,null,begin);
        }
        int mid=(begin+end)/2;
        boolean isExist=false;
        int index=0;
        V value=null;
        while(begin<end){
            mid=(begin+end)/2;
            Entry midEntry=this.entrys.get(mid);
            int com=compare((K)midEntry.getKey(),key);
            if(com == 0){
                break;
            }else{
                if(com>0){
                    //key>midEntry.getKey()
                    begin=mid+1;
                }else{
                    end=mid-1;
                }
            }
        }
        //查找结束
        if(begin<end){
            //在该节点中找到了此entry
            isExist=true;
            index=mid;
            value=this.entrys.get(mid).getValue();
        }else{
            K midKey=this.entrys.get(begin).getKey();
            int com =compare(midKey,key);
            if(com == 0){
                isExist=true;
                index=begin;
                value=this.entrys.get(begin).getValue();
            }else{
                if(com>0){
                    //key>midkey
                    isExist=false;
                    index=begin+1;
                    value=null;
                }else{
                    isExist=false;
                    index=begin;
                    value=null;
                }
            }
        }
        return new Result<V>(isExist,value,index);
    }

添加

由根节点开始,添加前进行判断,当节点存储个数已经达到最大个数,调用分裂,
分裂后调用查询方法将值插入。

   //添加第一步,判断根节点是否已满
    public void addNode(Entry<K, V> en) {
        if(root.getSize()==maxSiez){
            //根节点已满,请先分裂
            Node<K,V> newRoot=new Node<>();
            newRoot.setLeaf(false);
            newRoot.getChildren().add(0,root);
            splitNode(newRoot,root,0);
            this.root=newRoot;
        }
        addNodeNotFull(root ,en);
    }
    //添加第二步,递归找到所应插入的叶子节点
    public void addNodeNotFull(Node<K,V> root,Entry<K,V> entry){
        if(root.isLeaf()){
            //到达叶子节点
            addEntry(root,entry);
            return;
        }
        Result<V> result = root.search(entry.getKey());
        if(result.isExist()){
            return;
        }
        //不是叶子节点,根据查找出所在子节点下标获取子节点对象
        int index=result.getIndex();
        Node<K,V> searchChild=root.childAt(index);
        //判断子节点是否需要分裂
        if(searchChild.getSize()==2*t-1){
            splitNode(root,searchChild,index);
            //判断插入数据的key与分裂后顶上来的key谁大,决定插入在哪个分裂后的节点
            if(root.compare(root.getEntrys().get(index).getKey(),entry.getKey())>0){
                //entry Key大用分裂后的新节点,其下标为index+1
                searchChild=root.getChildren().get(index+1);
            }
        }
        addNodeNotFull(searchChild,entry);
    }

删除

保证父节点数据不为最小个数:
     逐级判断若某一节点已是最小个数,则想起左右兄弟节点中不为最小个数的借一位数,左右节点中的数顶替父节点中的数,父节点中的数进入本节点,且带着左右兄弟节点过户来的子节点,若左右兄弟节点都已经是最小个数,则与父节点中的值一起合并为新节点。保证父节点数据数量不为最小个数。
将该值向下旋转:
     将其旋转至叶子节点,判断其左右子节点,若存在一个子节点存储数据个数>最小个数,若为子节点,与其最大值交换,右节点与其最小值交换,交换完成后将其从叶子节点中删除;若左右子节点都已经是最小个数,则将该值从父节点移除,与左右子节点合并为一个新节点挂在父节点下,后在叶子节点中删除;

 public Entry<K,V> delete(Node<K,V> root,Entry<K,V> entry){
        Result<V> result = root.search(entry.getKey());
        if(result.isExist()){
            //该节点存在所要删除的数
            if(root.isLeaf()){
                //该节点为叶子节点
                return root.removeEntry(result.getIndex());
            }
            //若不是叶子节点,需要将该数据旋转至子节点直至叶子节点进行删除
            Node<K,V> leftChild=root.childAt(result.getIndex());
            //若左节点已经是最少数量t-1,则需交给右节点
            if(leftChild.getSize() >t-1){
                //左节点数量大于t-1,可进行旋转
                //删除步骤:将删除节点递归与左节点最后一位或右节点最左一位互换,一直换到叶子节点进行删除
                //互换
                root.removeEntry(result.getIndex());
                root.addEntry(result.getIndex(),leftChild.getEntrys().get(leftChild.getSize()-1));;
                leftChild.removeEntry(leftChild.getSize()-1);
                leftChild.addEntry(entry);
                return delete(leftChild,entry);
            }
            Node<K,V> rightNode=root.childAt(result.getIndex()+1);
            if(rightNode.getSize()>t-1){
                //右节点与左节点逻辑相似,只是将最左与删除项进行交换
                root.removeEntry(result.getIndex());
                root.addEntry(result.getIndex(),rightNode.getEntrys().get(0));;
                rightNode.removeEntry(0);
                rightNode.addEntry(0,entry);
                return delete(rightNode,entry);
            }
            //如果左右节点数据数量均已是最小值t-1,则将其合并,将删除项与有节点全部放入左节点
            leftChild.addEntry(entry);
            //左右子节点合并,移除右节点
            root.removeChild(result.getIndex()+1);
            root.removeEntry(result.getIndex());
            //将右节点的数据转移至左节点
            for(Entry<K,V> en:rightNode.getEntrys()){
                leftChild.addEntry(en);
            }
            //如果右节点不是存在子节点,则将其子节点也并入左节点
            if( ! rightNode.isLeaf()){
                for(Node<K,V> node:rightNode.getChildren()){
                    leftChild.insertChild(node);
                }
            }
            //合并后继续递归删除
            return delete(leftChild,entry);
        }else{
            //删除项不在本节点中,在其子节点中
            if(root.isLeaf()){
                //如果该节点已经是叶子节点,则表明不在其中
                System.out.println("所要删除数据不在本树中");
            }
            Node<K,V> searchChild=root.childAt(result.getIndex());
            //获取所在子节点,判断数量大于t-1则递归删除
            if(searchChild.getSize()>t-1){
                return delete(searchChild,entry);
            }
            //所在子节点数量已是最少的t-1,不能直接进入自己点删除,需准备旋转或合并
            //先找最亲近的兄弟借钱->兄弟有钱,旋转周转,都没钱一起去抢父母
            Node<K,V> brotherNode=null;
            //兄弟节点所在下标
            int brotherIndex=-1;
            if(result.getIndex()<root.getSize()){
                //存在右兄弟
                Node<K, V> rightBrother = root.childAt(result.getIndex() + 1);
                if(rightBrother.getSize()>t-1){
                    //如果右边兄弟有钱
                    brotherNode=rightBrother;
                    brotherIndex=result.getIndex()+1;
                }
            }
            if(brotherNode==null){
                //右兄弟没钱或者没有右兄弟,去找左兄弟
                if(result.getIndex()>0){
                    //存在左兄弟
                    Node<K, V> leftBrother = root.childAt(result.getIndex() - 1);
                    if(leftBrother.getSize()>t-1){
                        //左兄弟有钱
                        brotherNode=leftBrother;
                        brotherIndex=result.getIndex()-1;
                    }
                }
            }
            //左右兄弟能借钱
            if(brotherNode != null){
                if(brotherIndex < result.getIndex()){
                    //左兄弟借的钱,把父节点中左兄弟下标的数据拿到自己0下标位置,左兄弟最右侧数据塞给父节点
                    searchChild.addEntry(0,root.getEntrys().get(brotherIndex));
                    root.removeEntry(brotherIndex);
                    root.addEntry(brotherIndex,brotherNode.getEntrys().get(brotherNode.getSize()-1));
                    brotherNode.removeEntry(brotherNode.getSize()-1);
                    //如果左兄弟有孩子,孩子要过户,左孩子最大的孩子拿来给自己当最小的
                    if( ! brotherNode.isLeaf()){
                        searchChild.insertChild(0,brotherNode.childAt(brotherNode.getSize()));
                        brotherNode.removeChild(brotherNode.getSize()-1);
                    }
                }else{
                    //右兄弟借的钱,与左兄弟类似,拿右边,放最大
                    searchChild.addEntry(searchChild.getSize(),root.getEntrys().get(result.getIndex()));
                    root.removeEntry(result.getIndex());
                    root.addEntry(result.getIndex(),brotherNode.getEntrys().get(0));
                    brotherNode.removeEntry(0);
                    //如果右兄弟有孩子,同样要过户
                    if( ! brotherNode.isLeaf()){
                        //右兄弟最小的孩子拿来给自己当最大的
                        searchChild.insertChild(searchChild.getSize(),brotherNode.childAt(0));
                        brotherNode.removeChild(0);
                    }
                }
                //旋转结束,继续递归删除
                return delete(searchChild,entry);
            }
            //左右兄弟都没钱,把父节点拖一起借钱
            if(result.getIndex() < root.getSize()){
                //存在右兄弟
                Node<K,V> rightBrother=root.childAt(result.getIndex()+1);
                //本节点添加与右节点所在父节点的中间数据,父节点移除该数据,父节点移除该数据右子节点
                searchChild.addEntry(root.getEntrys().get(result.getIndex()));
                root.removeEntry(result.getIndex());
                root.removeChild(result.getIndex()+1);
                for(Entry<K,V> en:rightBrother.getEntrys()){
                    searchChild.addEntry(en);
                }
                if( ! rightBrother.isLeaf()){
                    //右兄弟不是叶子节点,把它的孩子都抢过来
                    for(Node<K,V> child:rightBrother.getChildren()){
                        searchChild.insertChild(child);
                    }
                }
            }else{
                //没有右兄弟,找左兄弟
                Node<K, V> leftBrother = root.childAt(result.getIndex() - 1);
                searchChild.addEntry(0,root.getEntrys().get(result.getIndex()-1));
                root.removeEntry(result.getIndex()-1);
                root.removeChild(result.getIndex()-1);
                //
                for(int i=leftBrother.getSize()-1;i>=0;i--){
                    searchChild.addEntry(0,leftBrother.getEntrys().get(i));
                }
                //如果左节点右孩子,过户
                if( ! leftBrother.isLeaf()){
                    for(int i=leftBrother.getSize();i>=0;i--){
                        searchChild.insertChild(leftBrother.getChildren().get(i));
                    }
                }
            }
            if (root == this.root && root.getSize() ==0) {
                this.root = searchChild;
            }
            return delete(searchChild,entry);
        }
    }

B+树原理与部分方法展示

本文所用B+树

本文展示的B+树为每个节点的最大值提升至父节点中如图
在这里插入图片描述此种方式在添加时添加值大于父节点中A值时需要去A值所对应的子节点中去插入,但是删除时需要去A+1值中去删除,所以增加与删除所对应的查询方法有所区别。
另一种将第二节点开始的最小值提至父节点的B+树应可避免该问题,未作展示请自行尝试;
在这里插入图片描述

分裂

与B树分裂相似,只不过将最后一位提至父节点中,且不删除。

//分裂节点
    public void splitNode(Node<V> root,Node<V> child,int index){
        //先分出一个空白宝宝准备作为大孩子
        Node<V> newBoby=new Node<>();
        newBoby.setLeaf(child.isLeaf());
        newBoby.setParentNode(root);
        if(child.isLeaf()){
            //如果是叶子节点,设置叶子链表
            newBoby.setNextNode(child.getNextNode());
            newBoby.setPreviousNode(child);
            child.setNextNode(newBoby);
        }
        //将原分裂节点后半部分孩子全部扔给新节点
        for(int i=minSize;i<maxSize;i++){
            newBoby.addKey(child.keyAt(i));
            if(child.isLeaf()){
                newBoby.addEntry(child.entryAt(i));
            }
        }
        //移除原分裂节点中后半部分的元素
        for(int i=maxSize-1;i>=minSize;i--){
            child.deleteKey(i);
            if(child.isLeaf()){
                child.deleteEntry(i);
            }
        }
        //将分裂后的俩个最大值拿出来准备更新及塞入父节点
        Integer newKey1=child.keyAt(child.getKeysSize()-1);
        Integer newKey2=newBoby.keyAt(newBoby.getKeysSize()-1);
        //如果分裂的节点不是叶子节点,它的孩子也要过户
        if( ! child.isLeaf()){
            for(int i=minSize;i<maxSize;i++){
                newBoby.addChild(child.childAt(i));
                child.childAt(i).setParentNode(newBoby);
            }
            //将过户的孩子扔掉
            for(int i=maxSize-1;i>=minSize;i--){
                child.deleteChild(i);
            }
        }
        //更新父节点中的值及子节点
        root.updateKey(index,newKey1);
        root.addKey(newKey2,index+1);
        root.addChild(newBoby,index+1);
    }

查询

查询需分为俩种情况,一种为添加时一种为删除时例如跟节点的key值为3,6,9
添加5时,需先去3所对应的子节点中去添加
删除5时需去6对应的子节点中去删除

添加查询

 //二分查找所查找key应该在节点哪
    public Result<V> search(Integer key ){
        int begin=0;int end=this.getKeysSize()-1;
        int mid;
        int index=-1;
        Entry<V> entry=null;
        //node为空直接返回
        if(this.getKeysSize() <=0){
            return new Result<>(null,entry,index,false);
        }
        //若节点为叶子节点且不存在
//        if(this.isLeaf() && ! this.getKeys().contains(key)){
//            return new Result<>(null,entry,index,false);
//        }
        while(begin<end){
            mid=(begin+end)/2;
            int com=this.keyAt(mid).compareTo(key);
            if(com==0){
                //中间值为所查找值
                index=mid;
                if(this.isLeaf()){
                    entry=this.entryAt(mid);
                }
                return new Result<>(key,entry,index,true);
            }else if(com <0){
                //key大
                begin=mid+1;
            }else{
                //mid大
                end=mid-1;
            }
        }
        int com = this.keyAt(begin).compareTo(key);
        if(com == 0){
            index=begin;
            if(this.isLeaf()){
                entry=this.entryAt(begin);
            }
            return new Result<>(key,entry,index,true);
        }else if(com < 0){
            //begin所在位置key小与查询key
            index=begin;
            if(this.isLeaf()){
                //如果不是叶子节点,子节点下边等同begin,如果是叶子节点,插入需要+1,因为这种B+树子节点少一个头
                index=begin+1;
            }
            return new Result<>(key,null,index,false);
        }else{
            //begin所在位置key大于查询key
            index=begin;
            return new Result<>(key,null,index,false);
        }
    }

删除查询

    //二分查找所查找key应该在节点哪
    public Result<V> deleteSearch(Integer key ){
        int begin=0;int end=this.getKeysSize()-1;
        int mid;
        int index=-1;
        Entry<V> entry=null;
        //node为空直接返回
        if(this.getKeysSize() <=0){
            return new Result<>(null,entry,index,false);
        }
        //若节点为叶子节点且不存在
//        if(this.isLeaf() && ! this.getKeys().contains(key)){
//            return new Result<>(null,entry,index,false);
//        }
        while(begin<end){
            mid=(begin+end)/2;
            int com=this.keyAt(mid).compareTo(key);
            if(com==0){
                //中间值为所查找值
                index=mid;
                if(this.isLeaf()){
                    entry=this.entryAt(mid);
                }
                return new Result<>(key,entry,index,true);
            }else if(com <0){
                //key大
                begin=mid+1;
            }else{
                //mid大
                end=mid-1;
            }
        }
        int com = this.keyAt(begin).compareTo(key);
        if(com == 0){
            index=begin;
            if(this.isLeaf()){
                entry=this.entryAt(begin);
            }
            return new Result<>(key,entry,index,true);
        }else if(com < 0){
            //begin所在位置key小与查询key
            index=begin + 1;
            return new Result<>(key,null,index,false);
        }else{
            //begin所在位置key大于查询key
            index=begin;
            return new Result<>(key,null,index,false);
        }
    }

添加

添加时判断第一次添加需设置头节点,其次判断其是否已经达到最大存储,满则分裂,不满则循环至叶子节点插入,若其为最大值,则需循环向上更新key值。

循环更新最大值
    /**
     * 循环更新父节点,传入更新的叶节点
     */
    private void doWhileUpdateBigestkey(Node<V> entry,Integer key ){
        while(entry != null && entry.getParentNode() != null) {
            Result<V> parentResult = entry.getParentNode().search(key);
            if(entry.getParentNode().keyAt(entry.getParentNode().getKeysSize() - 1) < key) {
                //如果也是父节点的最大值,继续向上变
                entry.getParentNode().updateKey(parentResult.getIndex(), key);
                entry=entry.getParentNode();
            }else{
                //不是父节点的最大key,只更换对应值
                entry.getParentNode().updateKey(parentResult.getIndex(),key);
                entry=null;
            }
        }
    }

删除

循环至叶子节点删除,若其为最大值,需向上更新最大值,合并节点与兄弟节点合并采用将所有值及节点全部挂至右侧兄弟,若头节点被移除需重新指定头节点,其余像兄弟借值与B树类似

   /**
     * 合并节点
     */
    private void mergeNode(Node<V> root, Node<V> node, Integer index){
        //需要借钱或合并--看是否存在左右兄弟
        Node<V> brotherNode = null;
        Integer brotherIndex=null;
        if(index < root.getKeysSize()-1){
            //存在右兄弟 准备开口
            Node<V> rightNode = root.childAt(index + 1);
            if(rightNode.getKeysSize() > minSize){
                //右兄弟有钱,准备抢
                brotherNode=rightNode;
                brotherIndex=index+1;
            }
        }
        if(brotherNode == null){
            //没有右兄弟,去看看有没有弟弟
            if(index > 0){
                //有弟弟 开口
                Node<V> leftNode = root.childAt(index - 1);
                if(leftNode .getKeysSize() > minSize){
                    //抢
                    brotherNode=leftNode;
                    brotherIndex=index-1;
                }
            }
        }
        if(brotherNode == null){
            //兄弟都没钱,找父母
            if(index < root.getKeysSize() - 1){
                //和右兄弟合体
                Node<V> rightNode = root.childAt(index + 1);
                root.deleteChild(index + 1);
                root.deleteKey(index);
                for (int i = 0; i < rightNode.getKeysSize(); i++){
                    node.addKey(rightNode.keyAt(i));
                    if(node.isLeaf()){
                        node.addEntry((rightNode.entryAt(i)));
                        node.setNextNode(null);
                    }else{
                        node.addChild(rightNode.getChildren().get(i));
                    }
                }
                if(this.root.getKeysSize() == 1){
                    this.root = node;
                }
            }else{
                //和左兄弟合体
                Node<V> leftNode = root.childAt(index - 1);
                root.deleteChild(index - 1);
                root.deleteKey(index - 1);
                if(head == leftNode){
                    head = node;
                }
                for (int i = leftNode.getKeysSize() - 1; i >= 0; i--){
                    node.addKey(leftNode.keyAt(i));
                    if(node.isLeaf()){
                        node.addEntry(node.getEntrys().get(i));
                        node.setPreviousNode(leftNode.getPreviousNode());
                        leftNode.getPreviousNode().setNextNode(node);
                    }else{
                        node.addChild(leftNode.getChildren().get(i));
                    }
                }
            }
        }else {
            //抢兄弟
            if(brotherIndex > index){
                //右兄弟
                Integer brotherKey = brotherNode.keyAt(0);
                brotherNode.deleteKey(0);
                node.addKey(brotherKey);
                if(node.isLeaf()){
                    //叶子节点
                    node.addEntry(brotherNode.entryAt(0));
                    brotherNode.deleteEntry(0);
                }else{
                    //非叶子节点
                    node.addChild(brotherNode.childAt(0));
                    brotherNode.deleteChild(0);
                }
                //循环更新最大值
                doWhileUpdateBigestkey(root,brotherKey);
            }else {
                //左兄弟
                Integer brotherKey = brotherNode.keyAt(brotherNode.getKeysSize() - 1);
                brotherNode.deleteKey(brotherNode.getKeysSize() - 1);
                node.addKey(brotherKey,0);
                if(node.isLeaf()){
                    //叶子节点
                    node.addEntry(brotherNode.entryAt(brotherNode.getChildrenSize() - 1));
                    brotherNode.deleteEntry(brotherNode.getChildrenSize() - 1);
                }else {
                    //非叶子节点
                    node.addChild(brotherNode.childAt(brotherNode.getChildrenSize() - 1), 0);
                    brotherNode.deleteChild(brotherNode.getKeysSize() - 1);
                }
                root.updateKey(index - 1, brotherKey);
            }
        }
    }

源码地址

https://github.com/FifthMemo/BTreeAndBTreePlus.git
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值