B+ 树是一种高效的数据结构,在实现过程中需要考虑到多个因素,比如节点的分裂合并、数据的查找插入删除等。实现思路如下:
- 创建一个 BplusTree 类,包含根节点和度数
- 定义节点类,包括节点类型、键值数组、指针数组等属性和方法
- 实现 B+ 树的查找算法,遍历节点进行递归查找
- 实现 B+ 树的插入算法,遍历节点寻找合适的位置进行插入,如果节点已满则进行分裂操作
- 实现 B+ 树的删除算法,遍历节点寻找要删除的键值,如果是叶子节点直接删除,否则将其后代节点的最小值替换到该节点中
- 实现 B+ 树的更新算法,首先进行查找操作,然后用插入删除的方式更新节点
代码如下
class Node {
constructor(isLeaf = false) {
this.isLeaf = isLeaf;
this.keys = [];
this.values = [];
this.parent = null;
this.next = null;
}
}
class BPlusTree {
constructor(degree) {
this.root = new Node(true);
this.degree = degree;
}
insert(key, value) {
let node = this.root;
while (!node.isLeaf) {
let i = 0;
while (i < node.keys.length && node.keys[i] < key) {
i++;
}
node = node.values[i];
}
this.insertIntoNode(node, key, value);
}
insertIntoNode(node, key, value) {
let i = 0;
while (i < node.keys.length && node.keys[i] < key) {
i++;
}
node.keys.splice(i, 0, key);
node.values.splice(i, 0, value);
if (node.keys.length === this.degree) {
this.splitNode(node);
}
}
splitNode(node) {
let right = new Node(node.isLeaf);
right.next = node.next;
node.next = right;
let midIndex = Math.floor(node.keys.length / 2);
let midKey = node.keys[midIndex];
for (let i = midIndex; i < node.keys.length; i++) {
right.keys.push(node.keys[i]);
right.values.push(node.values[i]);
if (!right.isLeaf) {
right.values[i - midIndex].parent = right;
}
}
node.keys.length = midIndex;
node.values.length = midIndex;
if (node === this.root) {
this.createNewRoot(node, midKey, right);
} else {
let parent = node.parent;
this.insertIntoNode(parent, midKey, right);
}
}
createNewRoot(left, key, right) {
let newRoot = new Node();
newRoot.keys.push(key);
newRoot.values.push(left);
newRoot.values.push(right);
left.parent = newRoot;
right.parent = newRoot;
this.root = newRoot;
}
delete(key) {
let [node, index] = this.findLeafNodeWithIndex(key);
if (index !== -1) {
node.keys.splice(index, 1);
node.values.splice(index, 1);
this.rebalance(node);
}
}
find(key) {
let [node, index] = this.findLeafNodeWithIndex(key);
return index !== -1 ? node.values[index] : null;
}
findLeafNodeWithIndex(key) {
let node = this.root;
let index = -1;
while (!node.isLeaf) {
let i = 0;
while (i < node.keys.length && node.keys[i] < key) {
i++;
}
node = node.values[i];
}
index = node.keys.findIndex(k => k === key);
return [node, index];
}
update(key, value) {
let [node, index] = this.findLeafNodeWithIndex(key);
if (index !== -1) {
node.values[index] = value;
}
}
rebalance(node) {
if (node === this.root) {
if (node.keys.length === 0 && !node.isLeaf) {
this.root = node.values[0];
this.root.parent = null;
}
return;
}
if (node.keys.length < Math.ceil(this.degree / 2) - 1) {
let parent = node.parent;
let leftSibling = node.prev();
let rightSibling = node.next;
let leftDiff = leftSibling !== null ? node.keys.length - leftSibling.keys.length : Infinity;
let rightDiff = rightSibling !== null ? node.keys.length - rightSibling.keys.length : Infinity;
if (leftSibling !== null && leftDiff <= rightDiff) {
this.borrowFromLeft(leftSibling, node, parent);
} else if (rightSibling !== null) {
this.borrowFromRight(rightSibling, node, parent);
} else if (leftSibling !== null) {
this.merge(leftSibling, node, parent);
}
this.rebalance(parent);
}
}
borrowFromLeft(left, node, parent) {
let borrowIndex = left.keys.length - 1;
let key = left.keys[borrowIndex];
let value = left.values[borrowIndex];
left.keys.length--;
left.values.length--;
node.keys.splice(0, 0, key);
node.values.splice(0, 0, value);
if (!node.isLeaf) {
value.parent = node;
}
parent.keys[parent.values.indexOf(node)] = node.keys[0];
}
borrowFromRight(right, node, parent) {
let borrowIndex = 0;
let key = right.keys[borrowIndex];
let value = right.values[borrowIndex];
right.keys.splice(borrowIndex, 1);
right.values.splice(borrowIndex, 1);
node.keys.push(key);
node.values.push(value);
if (!node.isLeaf) {
value.parent = node;
}
parent.keys[parent.values.indexOf(right)] = right.keys[0];
}
merge(left, node, parent) {
let mergeIndex = parent.values.indexOf(left);
let keyIndex = mergeIndex > 0 ? mergeIndex - 1 : 0;
let key = parent.keys[keyIndex];
left.keys.push(key, ...node.keys);
left.values.push(...node.values);
for (let i = 0; i < node.values.length; i++) {
if (!node.isLeaf) {
node.values[i].parent = left;
}
}
parent.keys.splice(keyIndex, 1);
parent.values.splice(mergeIndex, 1);
node.keys.length = 0;
node.values.length = 0;
}
}
Node.prototype.prev = function () {
let curr = this;
if (curr.isLeaf) {
while (curr !== null && curr.keys.length === 0) {
curr = curr.parent;
}
if (curr === null) {
return null;
}
let index = curr.next.values.findIndex(v => v === this);
if (index > 0) {
return curr.next.values[index - 1];
} else {
return curr;
}
} else {
return curr.values[curr.values.length - 1];
}
};
Node.prototype.next = function () {
let curr = this;
if (curr.isLeaf) {
while (curr !== null && curr.next !== null && curr.keys.length === 0) {
curr = curr.next;
}
return curr.next;
} else {
return curr.values[0];
}
};
要在HTML上可视化B+树的增删改查,可以利用HTML5中的Canvas元素。首先需要在HTML中嵌入一个Canvas元素:
<canvas id="myCanvas" width="800" height="600"></canvas>
然后,在JavaScript中写一个B+树类,并实现相应的方法,例如insert、delete、find和update。在调用这些方法时,可以在Canvas上绘制出B+树的图形以进行可视化展示。
下面是一个简单的示例,基于上面提供的 B+ 树实现代码以进行可视化展示。代码使用了Fabric.js库来简化Canvas的操作。
// 创建Canvas对象
var canvas = new fabric.Canvas('myCanvas');
// B+树节点大小和间距
var nodeSize = 40;
var levelGap = 80;
var siblingGap = 20;
// B+树节点模板
var nodeTemplate = new fabric.Rect({
width: nodeSize,
height: nodeSize / 2,
fill: 'white',
stroke: 'black',
strokeWidth: 2,
originX: 'center',
originY: 'center',
});
// B+树指针模板
var pointerTemplate = new fabric.Line([0, 0, 0, nodeSize], {
stroke: 'black',
strokeWidth: 2,
});
// B+树类
class BPlusTree {
constructor(degree) {
this.root = new Node(true);
this.degree = degree;
}
insert(key, value) {
let node = this.root;
while (!node.isLeaf) {
let i = 0;
while (i < node.keys.length && node.keys[i] < key) {
i++;
}
node = node.values[i];
}
this.insertIntoNode(node, key, value);
this.draw();
}
insertIntoNode(node, key, value) {
let i = 0;
while (i < node.keys.length && node.keys[i] < key) {
i++;
}
node.keys.splice(i, 0, key);
node.values.splice(i, 0, value);
if (node.keys.length === this.degree) {
this.splitNode(node);
}
}
splitNode(node) {
let right = new Node(node.isLeaf);
right.next = node.next;
node.next = right;
let midIndex = Math.floor(node.keys.length / 2);
let midKey = node.keys[midIndex];
for (let i = midIndex; i < node.keys.length; i++) {
right.keys.push(node.keys[i]);
right.values.push(node.values[i]);
if (!right.isLeaf) {
right.values[i - midIndex].parent = right;
}
}
node.keys.length = midIndex;
node.values.length = midIndex;
if (node === this.root) {
this.createNewRoot(node, midKey, right);
} else {
let parent = node.parent;
this.insertIntoNode(parent, midKey, right);
}
}
createNewRoot(left, key, right) {
let newRoot = new Node();
newRoot.keys.push(key);
newRoot.values.push(left);
newRoot.values.push(right);
left.parent = newRoot;
right.parent = newRoot;
this.root = newRoot;
}
delete(key) {
let [node, index] = this.findLeafNodeWithIndex(key);
if (index !== -1) {
node.keys.splice(index, 1);
node.values.splice(index, 1);
this.rebalance(node);
this.draw();
}
}
find(key) {
let [node, index] = this.findLeafNodeWithIndex(key);
return index !== -1 ? node.values[index] : null;
}
findLeafNodeWithIndex(key) {
let node = this.root;
let index = -1;
while (!node.isLeaf) {
let i = 0;
while (i < node.keys.length && node.keys[i] < key) {
i++;
}
node = node.values[i];
}
index = node.keys.findIndex(k => k === key);
return [node, index];
}
update(key, value) {
let [node, index] = this.findLeafNodeWithIndex(key);
if (index !== -1) {
node.values[index] = value;
this.draw();
}
}
rebalance(node) {
if (node === this.root) {
if (node.keys.length === 0 && !node.isLeaf) {
this.root = node.values[0];
this.root.parent = null;
}
return;
}
if (node.keys.length < Math.ceil(this.degree / 2) - 1) {
let parent = node.parent;
let leftSibling = node.prev();
let rightSibling = node.next;
let leftDiff = leftSibling !== null ? node.keys.length - leftSibling.keys.length : Infinity;
let rightDiff = rightSibling !== null ? node.keys.length - rightSibling.keys.length : Infinity;
if (leftSibling !== null && leftDiff <= rightDiff) {
this.borrowFromLeft(leftSibling, node, parent);
} else if (rightSibling !== null) {
this.borrowFromRight(rightSibling, node, parent);
} else if (leftSibling !== null) {
this.merge(leftSibling, node, parent);
}
this.rebalance(parent);
}
}
borrowFromLeft(left, node, parent) {
let borrowIndex = left.keys.length - 1;
let key = left.keys[borrowIndex];
let value = left.values[borrowIndex];
left.keys.length--;
left.values.length--;
node.keys.splice(0, 0, key);
node.values.splice(0, 0, value);
if (!node.isLeaf) {
value.parent = node;
}
parent.keys[parent.values.indexOf(node)] = node.keys[0];
}
borrowFromRight(right, node, parent) {
let borrowIndex = 0;
let key = right.keys[borrowIndex];
let value = right.values[borrowIndex];
right.keys.splice(borrowIndex, 1);
right.values.splice(borrowIndex, 1);
node.keys.push(key);
node.values.push(value);
if (!node.isLeaf) {
value.parent = node;
}
parent.keys[parent.values.indexOf(right)] = right.keys[0];
}
merge(left, node, parent) {
let mergeIndex = parent.values.indexOf(left);
let keyIndex = mergeIndex > 0 ? mergeIndex - 1 : 0;
let key = parent.keys[keyIndex];
left.keys.push(key, ...node.keys);
left.values.push(...node.values);
for (let i = 0; i < node.values.length; i++) {
if (!node.isLeaf) {
node.values[i].parent = left;
}
}
parent.keys.splice(keyIndex, 1);
parent.values.splice(mergeIndex, 1);
node.keys.length = 0;
node.values.length = 0;
}
// 在Canvas上绘制B+树
draw() {
canvas.clear();
let nodes = [];
let pointers = [];
this.traverse(this.root, nodes, pointers, 0);
for (let i = 0; i < nodes.length; i++) {
canvas.add(nodes[i]);
}
for (let i = 0; i < pointers.length; i++) {
canvas.add(pointers[i]);
}
}
// 中序遍历B+树,生成节点和指针对象数组
traverse(node, nodes, pointers, level) {
let x = nodeSize / 2 + (node.keys.length + 1) * siblingGap / 2;
let y = level * levelGap + nodeSize / 2;
let currNode = new fabric.Text(node.keys.join('\n'), {
left: x,
top: y,
fontFamily: 'Arial',
fontSize: 16,
originX: 'center',
originY: 'center',
});
let currPointer = null;
if (!node.isLeaf) {
for (let i = 0; i < node.values.length; i++) {
let childNode = node.values[i];
this.traverse(childNode, nodes, pointers, level + 1);
let childX = childNode.prev() ? childNode.prev().left + nodeSize + siblingGap : x - (node.keys.length - i) * (nodeSize + siblingGap);
currPointer = new fabric.Line([x, y, childX + nodeSize / 2, (level + 1) * levelGap], {
stroke: 'black',
strokeWidth: 2,
originX: 'center',
originY: 'center',
});
pointers.push(currPointer);
}
}
currNode.set({ left: x, top: y });
nodes.push(nodeTemplate.clone().set({ left: x, top: y }));
}
}
// 定义B+树节点类
class Node {
constructor(isLeaf = false) {
this.isLeaf = isLeaf;
this.keys = [];
this.values = [];
this.parent = null;
this.next = null;
}
prev() {
let curr = this;
if (curr.isLeaf) {
while (curr !== null && curr.keys.length === 0) {
curr = curr.parent;
}
if (curr === null) {
return null;
}
let index = curr.next.values.findIndex(v => v === this);
if (index > 0) {
return curr.next.values[index - 1];
} else {
return curr;
}
} else {
return curr.values[curr.values.length - 1];
}
}
next() {
let curr = this;
if (curr.isLeaf) {
while (curr !== null && curr.next !== null && curr.keys.length === 0) {
curr = curr.next;
}
return curr.next;
} else {
return curr.values[0];
}
}
}
// 创建B+树对象
var bptree = new BPlusTree(4);
// 监听HTML上的按键事件
document.addEventListener('keydown', function(event) {
var key = parseInt(event.key);
if (!Number.isNaN(key)) { // 如果按下的是数字键
if (event.shiftKey) { // 如果同时按下了Shift键,则执行删除操作
bptree.delete(key);
} else if (event.ctrlKey) { // 如果同时按下了Ctrl键,则执行更新操作
bptree.update(key, 'value' + key);
} else { // 否则执行插入操作
bptree.insert(key, 'value' + key);
}
}
});
使用上述代码,当用户在HTML页面中按下数字键时,会在B+树中插入相应的键值对;按下Shift加数字键时,会删除对应的键值对;按下Ctrl加数字键时,会更新对应键值对的值。同时,B+树的结构会在Canvas上进行实时绘制,以方便用户观察。