📅  最后修改于: 2023-12-03 15:23:22.098000             🧑  作者: Mango
B树是一种平衡的多路搜索树,常用于数据库和文件系统中。它具有多个关键字的特点,每个节点可以存储多个关键字和对应的指针,而且在插入操作后,能够自动保持平衡。
B树的插入操作可以分为两个步骤:定位待插入关键字的位置和执行插入操作。下面我们逐一介绍这两个步骤的实现。
在B树中插入一个关键字,需要找到它应该插入的位置。就像在二叉搜索树中查找关键字一样,我们需要从根节点开始,依次向下查找,直到找到合适的位置。但是,在B树中,因为每个节点可以存储多个关键字和对应的指针,所以我们需要进行一些调整。
当从根节点开始向下查找,如果遇到一个非叶子节点,我们需要按照节点中的关键字进行查找。如果待插入的关键字比节点中某个关键字小,则继续向节点的左子树查找;如果待插入的关键字比节点中某个关键字大,则继续向节点的右子树查找;如果待插入的关键字和节点中某个关键字相等,则直接返回。
如果遇到一个叶子节点,我们则需要在该叶子节点中插入待插入的关键字。如果该节点中已经有了待插入的关键字,则直接返回。
如果插入该关键字导致节点关键字数目超过了阈值(比如2-3树中的2或3),则需要进行分裂操作。我们先把该节点中的关键字按照从小到大的顺序排列,然后把中间的关键字提取出来,作为新的节点的关键字。该节点中左边的关键字和指针归为一个节点,右边的关键字和指针归为另一个节点。然后把中间关键字插入到该节点的父节点中。如果父节点也超过了阈值,则需要递归进行分裂操作。
在找到待插入关键字的位置后,我们需要执行插入操作。如果待插入的关键字已经存在于B树中,直接返回。否则,在该叶子节点中插入该关键字,然后递归检查父节点是否需要分裂。如果已经到达了根节点但是根节点超过了阈值,则需要进行根节点的拆分操作。
下面是C++实现的B树插入操作的代码示例:
// 插入一个关键字
void insert(Key k) {
if (root == nullptr) {
// 如果B树为空,则创建一个只包含该关键字的根节点
root = new node();
root->leaf = true;
root->keys.push_back(k);
return;
}
node* cur = root;
while (!cur->leaf) {
// 在非叶子节点中查找合适的子节点
auto it = lower_bound(cur->keys.begin(), cur->keys.end(), k);
int idx = it - cur->keys.begin();
if (it == cur->keys.end() || *it != k) {
cur = cur->kids[idx];
} else {
// 如果关键字已经存在,则直接返回
return;
}
}
// 在叶子节点中插入关键字
auto it = lower_bound(cur->keys.begin(), cur->keys.end(), k);
int idx = it - cur->keys.begin();
if (it == cur->keys.end() || *it != k) {
cur->keys.insert(it, k);
} else {
// 如果关键字已经存在,则直接返回
return;
}
// 检查是否需要分裂
while (cur != nullptr && cur->keys.size() > MAX_KEYS) {
node* parent = cur->parent;
// 将当前节点中间的关键字插入到父节点中
Key mid_key = cur->keys[MAX_KEYS / 2];
auto mid_it = parent->keys.insert(lower_bound(parent->keys.begin(), parent->keys.end(), mid_key), mid_key);
int mid_idx = mid_it - parent->keys.begin();
// 将当前节点分裂成两个节点
node* left = new node();
node* right = new node();
if (cur->leaf) {
// 如果当前节点是叶子节点,则直接拆分关键字列表
left->keys.insert(left->keys.end(), cur->keys.begin(), cur->keys.begin() + MAX_KEYS / 2);
right->keys.insert(right->keys.end(), cur->keys.begin() + MAX_KEYS / 2, cur->keys.end());
} else {
// 如果当前节点是非叶子节点,则需要把指针也拆分
left->kids.insert(left->kids.end(), cur->kids.begin(), cur->kids.begin() + MAX_KEYS / 2 + 1);
right->kids.insert(right->kids.end(), cur->kids.begin() + MAX_KEYS / 2 + 1, cur->kids.end());
for (auto k = left->kids.begin(); k != left->kids.end(); ++k) {
(*k)->parent = left;
}
for (auto k = right->kids.begin(); k != right->kids.end(); ++k) {
(*k)->parent = right;
}
}
left->leaf = cur->leaf;
right->leaf = cur->leaf;
// 将新节点挂到父节点下面
parent->kids.insert(parent->kids.begin() + mid_idx, left);
parent->kids.insert(parent->kids.begin() + mid_idx + 1, right);
left->parent = parent;
right->parent = parent;
// 更新当前节点
cur = parent;
}
// 如果根节点超过了阈值,则需要进行根节点的拆分操作
if (root->keys.size() > MAX_KEYS) {
node* left = new node();
node* right = new node();
left->keys.insert(left->keys.end(), root->keys.begin(), root->keys.begin() + MAX_KEYS / 2);
right->keys.insert(right->keys.end(), root->keys.begin() + MAX_KEYS / 2, root->keys.end());
left->kids.insert(left->kids.end(), root->kids.begin(), root->kids.begin() + MAX_KEYS / 2 + 1);
right->kids.insert(right->kids.end(), root->kids.begin() + MAX_KEYS / 2 + 1, root->kids.end());
for (auto k = left->kids.begin(); k != left->kids.end(); ++k) {
(*k)->parent = left;
}
for (auto k = right->kids.begin(); k != right->kids.end(); ++k) {
(*k)->parent = right;
}
left->leaf = false;
right->leaf = false;
root->keys.clear();
root->kids.clear();
root->keys.push_back(right->keys[0]);
root->kids.push_back(left);
root->kids.push_back(right);
left->parent = root;
right->parent = root;
}
}
该实现使用了C++ STL中的vector和algorithm库,可以方便地进行查找和排序。一个关键字的类型为Key,可以是字符串、整数、浮点数等等。
B树的插入操作是一种比较复杂的操作,需要进行关键字的定位、节点的分裂等步骤。在实际应用中,我们通常采用现有的B树库来进行实现,可以大大减少开发时间和错误率。