B-tree B树的JS实现(三):删除元素

首先,B树必须满足:元素数=子节点数 - 1元素数 > CEIL(M/2) - 1

删除元素,会导致上述条件不满足。

对于可能会导致这种不满足的删除,需要考虑下面几种情况:

一:删除元素后,如果删除节点(删除元素所在的节点,简称删除节点,下同)的叶子节点有丰满元素(即满足 元素数 > CEIL(M/2) - 1,称丰满元素),向子节点借元素,子节点再向它的子节点借,直到叶子节点。(因为只有叶子节点可以不用遵守 "元素数=子节点数 - 1" 这个原则)

二:叶子节点没有丰满元素,子节点有,向子节点借后,子节点的子节点进行合并(从叶子节点开始向上合并,直到借出元素的节点,借出元素的节点不合并)。

三:以上都不满足,向兄弟节点借。借的元素给父节点,然后父节点给删除节点一个元素。然后有以下两种情况:

        1:兄弟节点有丰满元素,兄弟节点的叶子节点也有丰满元素,兄弟节点再向它的子节点借一个元素。

        2:兄弟节点有丰满元素,但是兄弟节点的子节点没有丰满元素。兄弟节点借出元素后,把兄弟节点的一支子节点同时也给到删除节点。然后删除节点合并一条子节点。(因为给删除节点一个子节点后,会导致“元素数<子节点数 - 1”,同时可以合并子节点是因为子节点没有丰满元素,可以合并)。

四:以上条件都不满足的,向父节点借一个元素,同时将删除节点和兄弟节点合并。然后父节点再向它的父节点借一个元素,再合并父节点和父节点的兄弟节点。重复以上操作,直到合并到根节点。

const M = 5;
const LEAST = Math.ceil(M/2) - 1;

class BTree {
    constructor() {
        /*只需要入口节点,即根节点*/
        this.root = null;
    }
    addKey(key){
        if(this.root === null){
            this.root = new BTreeNode();
            this.root.add(key);
            return 1;
        }
        if(this.root.pages.length === 0){
            this.root.add(key);
            return 1;
        }
        /*一直遍历到叶子节点*/
        let node = this.root;
        while(node.pages.length > 0){
            for(let i in node.keys){
                i = parseInt(i);
                if(key === node.keys[i]){
                    return 0;
                }
                if(key < node.keys[i]){
                    node = node.pages[i];
                    break;
                }else if (key > node.keys[node.keys.length - 1]){
                    node = node.pages[node.pages.length - 1];
                    break;
                }else if(key > node.keys[i] && key < node.keys[i+1]){
                    node = node.pages[i+1];
                    break;
                }
            }
        }
        node.add(key);
        return 1;
    }
    deleteKey(key){
        let node = this.root;
        let index; //删除元素所在的位置
        let parentIndex; //删除元素在其父节点处的pages下标

        while(true){
            let find = false;
            let inChild = false;
            if(key < node.keys[0]){
                node = node.pages[0];
                parentIndex = 0;
                continue;
            }else if(key > node.keys[node.keys.length - 1]){
                parentIndex = node.pages.length - 1;
                node = node.pages[node.pages.length - 1];
                continue;
            }else{
                for(let i in node.keys){
                    i = parseInt(i);
                    if(key > node.keys[i] && key < node.keys[i+1]) {
                        node = node.pages[i + 1];
                        parentIndex = i+1;
                        inChild = true;
                        break;
                    }
                    if(key === node.keys[i]){
                        find = true;
                        node.keys.splice(i, 1);
                        index = i;
                        break;
                    }
                }
                if(inChild){
                    continue;
                }
            }
            if(node.pages.length <= 0 && !find){
                throw new Error(`没有发现${key}元素`);
            }
            if(find){
                break;
            }
        }

        if(null === index ){
            throw new Error("删除失败,未查找到元素!");
        }
        if(node.pages.length <= 0 && node.keys.length >= LEAST){
            return true;
        }
        /*如果删除后,当前节点key小于Math.ceil(M/2) - 1,需要向其他节点借元素
        * 如果子有丰满节点,向子借,子不满,再向子的子找,一直遍历到叶子节点
        * 注意,这里的子只找对应的子,即删除节点的左右子
        * 左子找最大元素的左右子,右子找最小元素的左右子,依次类推
        * 如果还没有
        * 如果父有额外元素,向父借
        * 如果父没有,那就向兄弟节点借
        * 如果以上都不满足,则需要进行节点合并
        * 第一部,遍历到子一直到叶子节点,从叶子节点开始合并,直到本节点
        * 第二步,向父借一个元素,然后将本节点和兄弟节点合并
        * 然后再来到父节点,父节点的父节点如果没有足够元素,向它的其它子节点借,
        * 如果还没有,则向父节点的父节点借一个元素,合并
        * 父节点,重复以上步骤只到借到元素或者到达根节点*/
        /*先看子*/
        if(node.keys.length < LEAST ){
            if(this.canBorrowKeyFromChild(node, index)){
                return true;
            }
        }
        /*子没有,看兄弟节点,如果可以向兄弟节点借,如果兄弟节点可以借,那么借完后,此时兄弟节点会导致keys.length < pages.length - 1,
        兄弟节点必须再向他的子借,如果兄弟节点的子节点借不到,那么兄弟节点提供元素的同时,把自己的一个子节点也提供过去,
        但是注意,删除节点的子节点必须子节点合并,以保证 keys.length === pages.length - 1*/
        let brotherNode;
        let brotherIndex;
        if(node !== this.root){
            for(let i in node.parent.pages){
                i = parseInt(i);
                if(node === node.parent.pages[i]){
                    if(i === node.parent.pages.length - 1 && node.parent.pages[node.parent.pages.length - 2].keys.length > LEAST){
                        /*如果是第一个子,只看后一个兄弟*/
                        node.keys.unshift(node.parent.keys.pop());
                        node.parent.keys.push(node.parent.pages[node.parent.pages.length - 2].keys.pop());
                        brotherNode = node.parent.pages[node.parent.pages.length - 2];
                        brotherIndex = brotherNode.keys.length - 1;
                    }else if(i === 0 && node.parent.pages[1].keys.length > LEAST){
                        /*如果是最后一个子,只看前一个兄弟*/
                        node.keys.push(node.parent.keys.shift());
                        node.parent.keys.unshift(node.parent.pages[1].keys.shift());
                        brotherNode = node.parent.pages[1];
                        brotherIndex = 0;
                    }else if(node.parent.pages[i - 1].keys.length > LEAST){
                        /*如果左兄弟丰满*/
                        node.keys.unshift.apply(node.keys, node.parent.keys.splice(i-1, 1));
                        node.parent.keys.unshift(node.parent.pages[i - 1].keys.pop());
                        brotherNode = node.parent.pages[i - 1];
                        brotherIndex = brotherNode.keys.length - 1;
                    }else if(node.parent.pages[i + 1].keys.length > LEAST){
                        /*如果右兄弟丰满*/
                        node.keys.push.apply(node.keys, node.parent.keys.splice(i, 1));
                        node.parent.keys.push(node.parent.pages[i + 1].keys.shift());
                        brotherNode = node.parent.pages[i + 1];
                        brotherIndex = 0;
                    }
                    if(brotherNode){
                        if(!brotherNode.pages.length){
                            return true;
                        }
                        if(!this.canBorrowKeyFromChild(brotherNode, brotherIndex)){
                            /*兄弟节点的子节点没有元素可借,那么兄弟节点提供元素的同时,将一条子节点给到删除节点
                            * 同时删除节点依然要进行节点合并*/
                            if(brotherIndex === 0){
                                brotherNode.pages[0].parent = node;
                                node.pages.push(brotherNode.pages.shift());
                            }else{
                                brotherNode.pages[brotherNode.pages.length - 1].parent = node;
                                node.pages.unshift(brotherNode.pages.pop());
                            }
                            if(node.pages.length > 0){
                                /*同时删除节点依然要进行节点合并*/
                                this.mergeChildNode(node.pages[0], 0);
                            }
                        }
                        return true;
                    }
                    break;
                }
            }
        }
        /*以上条件都不满足,开始合并节点,遍历到对应的叶子节点开始合并
        * 注意只合并其中一条线
        * 因为分裂时分裂的左侧,所以合并时还是优先合并左子*/
        if(node.pages.length > 0){
            /*如果有子,就合并最左边的子*/
            this.mergeChildNode(node.pages[0], 0);
        }
        /*子合完,开始向父借元素,再合并自己和兄弟节点*/
        this.mergeNodeToRoot(node);
    }
    /*
    leftOrRight @param true is left, and false is right
    如果是left,那么就返回自己的最大值,如果是right,就返回自己的最小值
    如果是left,那么就只找最大值的左右子,如果是right,就找最小值的左右子
    * */
    canBorrowKeyFromChild(node, index){
        if(node.pages.length > 0){
            let childKey = this.borrowKeyFromChild(node.pages[index], true);
            if(!childKey){
                childKey = this.borrowKeyFromChild(node.pages[index + 1], false);
            }
            if(childKey){
                node.keys.splice(index, 0, childKey);
                return true;
            }
        }
        return null;
    }
    borrowKeyFromChild(node, leftOrRight){
        /*已经到达叶子节点*/
        if(node.pages.length <= 0){
            if(node.keys.length > LEAST){
                if(leftOrRight){
                    /*左子给最大*/
                    return node.keys.pop();
                }else{
                    /*右子给最小*/
                    return node.keys.shift();
                }
            }else{
                return null;
            }
        }else{
            /*否则继续向下递归*/
            if(leftOrRight){
                let childKey = this.borrowKeyFromChild(node.pages[node.pages.length - 1], false);
                if(!childKey){
                    childKey = this.borrowKeyFromChild(node.pages[node.pages.length - 2], true)
                }
                if(childKey){
                    let tempKey = node.keys.pop();
                    node.keys.push(childKey);
                    return tempKey;
                }
            }else{
                let childKey = this.borrowKeyFromChild(node.pages[0], true);
                if(!childKey){
                    childKey = this.borrowKeyFromChild(node.pages[1], false)
                }
                if(childKey){
                    let tempKey = node.keys.shift();
                    node.keys.unshift(childKey);
                    return tempKey;
                }
            }
        }
        /**/
        return null;
    }

    /*向下合并,如果开始操作这步,说明子已经是不丰满了,可以直接合并
    *@param parentIndex node位于parentIndex处的下标*/

    mergeChildNode(node, parentIndex){
        if(node.pages.length > 0){
            this.mergeChildNode(node.pages[0], 0);
        }
        let otherNode;
        /*合并自己和兄弟节点*/
        if(parentIndex === 0 ){
            otherNode = node.parent.pages[1];
            this.mergeNode(node, otherNode, true);
        }else{
            otherNode = node.parent.pages[parentIndex - 1];
            this.mergeNode(node, otherNode, false);
        }
    }
    /*向上合并*/
    mergeNodeToRoot(node){
        while(node !== this.root){
            /*向父节点借一个元素,然后把本节点和兄弟节点合并*/
            for(let i in node.parent.pages){
                i = parseInt(i);
                if(node === node.parent.pages[i]){
                    if(i === node.parent.pages.length - 1){
                        node.add(node.parent.keys.splice(i - 1, 1)[0]);
                        this.mergeNode(node, node.parent.pages[i-1], false);
                    }else{
                        node.add(node.parent.keys.splice(i, 1)[0]);
                        this.mergeNode(node, node.parent.pages[i+1], true);
                    }
                    break;
                }
            }
            node = node.parent;
        }
        this.root = node.pages[0]; //此时必定只有一个子了,这个子作为新的root
    }

    mergeNode(originNode, deleteNode, isPush){
        if(isPush){
            originNode.keys = originNode.keys.concat(deleteNode.keys);
            originNode.pages = originNode.pages.concat(deleteNode.pages);

        }else{
            originNode.keys = deleteNode.keys.concat(originNode.keys);
            originNode.pages = deleteNode.pages.concat(originNode.pages);
        }
        for(let i in deleteNode.parent.pages){
            if(deleteNode.parent.pages[i] === deleteNode){
                deleteNode.parent.pages.splice(i, 1);
                break;
            }
        }
        for(let i in deleteNode.pages){
            deleteNode.pages[i].parent = originNode;
        }
        deleteNode = null;
    }

}

class BTreeNode {
    constructor() {
        this.parent = null;
        this.keys = [];
        this.pages = [];
    }
    add(key){
        if(this.keys.length === 0){
            this.keys.push(key);
            return this.keys.length;
        }
        if(this.keys[0] > key){
            /*插入头部*/
            this.keys.unshift(key);
        }else if(this.keys[this.keys.length - 1] < key){
            /*插入尾部*/
            this.keys.push(key);
        }else{
            /*插入中间*/
            for(let i in this.keys){
                i = parseInt(i);
                if(key < this.keys[i+1] && key > this.keys[i]){
                    this.keys.splice(i+1, 0, key);
                    break;
                }
            }
        }
        if(this.keys.length <= M - 1){
            return this.keys.length;
        }
        /*一个节点数ceil(M/2) + 1 <= n <= M - 1*/
        /*当节点数满了后,需要分裂*/
        /*第一种分裂方式,中间的关键字上移
        * ,左右的关键字变成左右子
        * 每次插入的时候肯定都是子节点
        *
        * 分裂后,又可能导致非叶子节点的查出从而导致非叶子节点分裂
        * */
        if(this.parent == null){
            let leftChild = new BTreeNode();
            let rightChild = new BTreeNode();
            leftChild.keys = this.keys.slice(0, Math.floor(this.keys.length/2));
            rightChild.keys = this.keys.slice(Math.floor(this.keys.length/2) + 1, this.keys.length);
            leftChild.parent = this;
            rightChild.parent = this;
            this.keys = [this.keys[Math.floor(this.keys.length/2)]];
            leftChild.pages = this.pages.slice(0, Math.floor(this.pages.length/2));
            /*重新分配子的父节点*/
            for(let i in leftChild.pages){
                i = parseInt(i);
                leftChild.pages[i].parent = leftChild;
            }
            rightChild.pages = this.pages.slice(Math.floor(this.pages.length/2), this.pages.length);
            for(let i in rightChild.pages){
                i = parseInt(i);
                rightChild.pages[i].parent = rightChild;
            }
            this.pages = [leftChild, rightChild];
        }else{
            /*先将新建的节点连接好,顺序不能错,否则分裂时丢失节点*/
            /*先将一半的key分出去*/
            let leftChild = new BTreeNode();
            leftChild.keys = this.keys.slice(0, Math.floor(this.keys.length/2));
            leftChild.parent = this.parent;
            for(let i in this.parent.pages){
                i = parseInt(i);
                /*将新建的节点连接到父节点的pages上*/
                if(this.parent.pages[i] === this){
                    this.parent.pages.splice(i, 0, leftChild);
                    break;
                }
            }

            /*中间的key给父节点*/
            /*这里给父节点一个元素后,会导致父节点分裂,需要重新定位父节点*/
            this.parent.add(this.keys[Math.floor(this.keys.length/2)]);
            /*自己保留右边*/
            this.keys = this.keys.slice(Math.floor(this.keys.length/2) + 1, this.keys.length);

            /*本节点的pages左边分配给左边,保留右边*/
            leftChild.pages =[].concat(this.pages.slice(0, Math.floor(this.pages.length/2)));
            for(let i in leftChild.pages){
                i = parseInt(i);
                leftChild.pages[i].parent = leftChild;
            }
            this.pages = [].concat(this.pages.slice(Math.floor(this.pages.length/2), this.pages.length));
        }
    }
}

let btree = new BTree();
/*btree.addKey(53);
btree.addKey(75);
btree.addKey(139);
btree.addKey(49);
btree.addKey(145);
btree.addKey(36);
btree.addKey(101);*/

btree.addKey('A');
btree.addKey('C');
btree.addKey('N');
btree.addKey('G');
btree.addKey('H');
btree.addKey('E');
btree.addKey('K');
btree.addKey('Q');
btree.addKey('M');
btree.addKey('F');
btree.addKey('W');
btree.addKey('L');
btree.addKey('T');
btree.addKey('Z');
btree.addKey('D');
btree.addKey('P');
btree.addKey('R');
btree.addKey('X');
btree.addKey('Y');
btree.addKey('S');
btree.deleteKey('H');
btree.deleteKey('T');
btree.deleteKey('R');
btree.deleteKey('E');
console.log(123);
/*btree.addKey('A');
btree.addKey('B');
btree.addKey('C');
btree.addKey('D');
btree.addKey('E');
btree.addKey('F');
btree.addKey('G');
btree.addKey('I');
btree.addKey('J');
btree.addKey('K');
btree.addKey('L');
btree.addKey('M');
btree.addKey('N');
btree.addKey('P');
btree.addKey('R');
btree.addKey('S');
btree.addKey('T');
btree.addKey('U');
btree.addKey('X');
btree.addKey('Z');
btree.deleteKey('C');
console.log(123);*/

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值