📅  最后修改于: 2023-12-03 15:13:44.142000             🧑  作者: Mango
B树是一种常见的自平衡树,常被用于数据库索引等场景。B树支持高效的插入和删除操作,本篇将介绍B树中的删除操作。
B树是一种多叉树,每个节点存储多个关键字和指向子节点的指针。B树节点的结构如下:
struct BTreeNode {
int n; // 节点中关键字的数量
int keys[MAX_KEYS]; // 节点中存储的关键字
struct BTreeNode* children[MAX_CHILDREN]; // 指向子节点的指针
bool leaf; // 节点是否为叶子节点
};
B树的每个节点中最多包含MAX_KEYS
个关键字,最多有MAX_CHILDREN
个子节点。B树中每个非根节点都至少有MAX_KEYS/2
个关键字,最少有MAX_KEYS/2
个子节点。B树的根节点可以是任意节点,其关键字数量不小于1。
删除B树中的关键字需要分解为两个步骤:
在B树中查找关键字和插入关键字非常类似。首先从根节点开始,遍历节点中的关键字和子节点。如果查找的关键字小于当前节点中的第一个关键字,则遍历左侧子节点;否则如果查找的关键字大于当前节点中的最后一个关键字,则遍历右侧子节点。如果查找的关键字在当前节点中,则返回该节点和关键字在节点中的位置。
如果遍历到叶子节点,但是没有找到关键字,则表示该关键字不存在于B树中。
删除B树中的关键字可能会导致B树不再满足平衡条件。因此需要在删除操作的过程中维护B树的平衡。
删除的操作需要分为两种情况:
对于第一种情况,如果删除关键字后节点中的关键字数量小于MAX_KEYS/2
,则需要作出以下调整:
MAX_KEYS/2
,则将该节点中的一个关键字借给相邻节点,同时从相邻节点中移动一个关键字到该节点中;MAX_KEYS/2
个关键字,则合并这两个节点,并将合并后的节点插入其父节点中,以保持平衡;对于第二种情况,可以将该关键字在其后继节点中进行替换,然后在后继节点中递归删除该关键字。如果删除后节点中的关键字数量小于MAX_KEYS/2
,也需要进行平衡调整。
下面是C++实现的B树中的删除操作示例代码。
void BTree::remove(int key) {
if (root == NULL) {
return;
}
removeHelper(root, key);
if (root->n == 0) {
BTreeNode* tmp = root;
if (root->leaf) {
root = NULL;
} else {
root = root->children[0];
}
delete tmp;
}
}
void BTree::removeHelper(BTreeNode* node, int key) {
int idx = findIndex(node, key);
if (idx < node->n && node->keys[idx] == key) { // Case 1: Found the key in the leaf node
if (node->leaf) {
removeFromLeafNode(node, idx);
} else { // Case 2: Found the key in the non-leaf node
removeFromNonLeafNode(node, idx);
}
} else {
if (node->leaf) { // Case 3: The key isn't in the tree
return;
}
bool flag = (idx == node->n);
if (node->children[idx]->n < MIN_KEYS) {
fill(idx);
}
if (flag && idx > node->n) {
removeHelper(node->children[idx - 1], key);
} else {
removeHelper(node->children[idx], key);
}
}
}
void BTree::removeFromLeafNode(BTreeNode* node, int idx) {
for (int i = idx + 1; i < node->n; ++i) {
node->keys[i - 1] = node->keys[i];
}
node->n--;
}
void BTree::removeFromNonLeafNode(BTreeNode* node, int idx) {
int k = node->keys[idx];
if (node->children[idx]->n >= MIN_KEYS) {
int pred = getPredecessor(node, idx);
node->keys[idx] = pred;
removeHelper(node->children[idx], pred);
} else if (node->children[idx + 1]->n >= MIN_KEYS) {
int succ = getSuccessor(node, idx);
node->keys[idx] = succ;
removeHelper(node->children[idx + 1], succ);
} else {
mergeNodes(node, idx);
removeHelper(node->children[idx], k);
}
}
int BTree::getPredecessor(BTreeNode* node, int idx) {
BTreeNode* curr = node->children[idx];
while (!curr->leaf) {
curr = curr->children[curr->n];
}
return curr->keys[curr->n - 1];
}
int BTree::getSuccessor(BTreeNode* node, int idx) {
BTreeNode* curr = node->children[idx + 1];
while (!curr->leaf) {
curr = curr->children[0];
}
return curr->keys[0];
}
void BTree::fill(int idx) {
if (idx != 0 && root->children[idx - 1]->n >= MIN_KEYS) {
borrowFromPrev(idx);
} else if (idx != root->n && root->children[idx + 1]->n >= MIN_KEYS) {
borrowFromNext(idx);
} else {
if (idx != root->n) {
mergeNodes(root, idx);
} else {
mergeNodes(root, idx - 1);
}
}
}
void BTree::borrowFromPrev(int idx) {
BTreeNode* child = root->children[idx];
BTreeNode* sibling = root->children[idx - 1];
for (int i = child->n - 1; i >= 0; --i) {
child->keys[i + 1] = child->keys[i];
}
if (!child->leaf) {
for (int i = child->n; i >= 0; --i) {
child->children[i + 1] = child->children[i];
}
}
child->keys[0] = root->keys[idx - 1];
if (!child->leaf) {
child->children[0] = sibling->children[sibling->n];
}
root->keys[idx - 1] = sibling->keys[sibling->n - 1];
child->n++;
sibling->n--;
}
void BTree::borrowFromNext(int idx) {
BTreeNode* child = root->children[idx];
BTreeNode* sibling = root->children[idx + 1];
child->keys[child->n] = root->keys[idx];
if (!child->leaf) {
child->children[child->n + 1] = sibling->children[0];
}
root->keys[idx] = sibling->keys[0];
for (int i = 1; i < sibling->n; ++i) {
sibling->keys[i - 1] = sibling->keys[i];
}
if (!sibling->leaf) {
for (int i = 1; i <= sibling->n; ++i) {
sibling->children[i - 1] = sibling->children[i];
}
}
child->n++;
sibling->n--;
}
void BTree::mergeNodes(BTreeNode* node, int idx) {
BTreeNode* child = node->children[idx];
BTreeNode* sibling = node->children[idx + 1];
child->keys[MIN_KEYS - 1] = node->keys[idx];
for (int i = 0; i < sibling->n; ++i) {
child->keys[i + MIN_KEYS] = sibling->keys[i];
}
if (!child->leaf) {
for (int i = 0; i <= sibling->n; ++i) {
child->children[i + MIN_KEYS] = sibling->children[i];
}
}
for (int i = idx + 1; i < node->n; ++i) {
node->keys[i - 1] = node->keys[i];
}
for (int i = idx + 2; i <= node->n; ++i) {
node->children[i - 1] = node->children[i];
}
child->n = 2 * MIN_KEYS - 1;
node->n--;
delete sibling;
}
本篇介绍了B树中的删除操作,其中涉及到的平衡调整方法有借关键字、合并节点等。相比插入操作,删除操作需要更加复杂的调整,但B树仍能够在对数时间复杂度内完成删除操作。