首先,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);*/