📜  在B树中插入操作(1)

📅  最后修改于: 2023-12-03 15:23:22.098000             🧑  作者: Mango

在B树中插入操作

B树是一种平衡的多路搜索树,常用于数据库和文件系统中。它具有多个关键字的特点,每个节点可以存储多个关键字和对应的指针,而且在插入操作后,能够自动保持平衡。

插入操作的实现

B树的插入操作可以分为两个步骤:定位待插入关键字的位置和执行插入操作。下面我们逐一介绍这两个步骤的实现。

1. 定位待插入关键字的位置

在B树中插入一个关键字,需要找到它应该插入的位置。就像在二叉搜索树中查找关键字一样,我们需要从根节点开始,依次向下查找,直到找到合适的位置。但是,在B树中,因为每个节点可以存储多个关键字和对应的指针,所以我们需要进行一些调整。

当从根节点开始向下查找,如果遇到一个非叶子节点,我们需要按照节点中的关键字进行查找。如果待插入的关键字比节点中某个关键字小,则继续向节点的左子树查找;如果待插入的关键字比节点中某个关键字大,则继续向节点的右子树查找;如果待插入的关键字和节点中某个关键字相等,则直接返回。

如果遇到一个叶子节点,我们则需要在该叶子节点中插入待插入的关键字。如果该节点中已经有了待插入的关键字,则直接返回。

如果插入该关键字导致节点关键字数目超过了阈值(比如2-3树中的2或3),则需要进行分裂操作。我们先把该节点中的关键字按照从小到大的顺序排列,然后把中间的关键字提取出来,作为新的节点的关键字。该节点中左边的关键字和指针归为一个节点,右边的关键字和指针归为另一个节点。然后把中间关键字插入到该节点的父节点中。如果父节点也超过了阈值,则需要递归进行分裂操作。

2. 执行插入操作

在找到待插入关键字的位置后,我们需要执行插入操作。如果待插入的关键字已经存在于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树库来进行实现,可以大大减少开发时间和错误率。