我正在致力于为 B 树实现非抢占式分裂插入功能。下面是我的 B 树节点的结构:
#define KEYS_NUMBER 4 // Max number of keys in a node
#define MIN_KEYS (KEYS_NUMBER / 2) // Min number of keys in a node
#define CHILDREN_NUMBER (KEYS_NUMBER + 1) // Max number of children in a node
typedef struct node {
bool leaf;
int n;
int keys[KEYS_NUMBER];
struct node *children[CHILDREN_NUMBER];
} Node;
我的 insertNonFull 和 insertKey 函数有以下代码:
void insertNonFull(Node *node, int key) {
int i = node->n - 1;
if (node->leaf) {
while (i >= 0 && (key < node->keys[i])) {
node->keys[i + 1] = node->keys[i];
i--;
}
node->keys[i + 1] = key;
node->n = node->n + 1;
} else {
while (i >= 0 && key < node->keys[i]) {
i--;
}
i++;
if (node->children[i]->n == KEYS_NUMBER) {
splitChild(node, i, node->children[i]);
if (key > node->keys[i]) {
i++;
}
}
insertNonFull(node->children[i], key);
}
}
void insertKey(Node **tree, int key) {
Node *r = *tree;
if (r->n == KEYS_NUMBER) {
Node *s = createNode();
*tree = s;
s->leaf = false;
s->n = 0;
s->children[0] = r;
splitChild(s, 0, r);
insertNonFull(s, key);
} else {
insertNonFull(r, key);
}
}
当我添加键15时,插入前后的B树结构如下:
插入第 15 把钥匙之前:
[3, 6, 9, 12]
[1, 2]
[4, 5]
[7, 8]
[10, 11]
[13, 14]
插入第15把钥匙后:
[9]
[3, 6]
[1, 2]
[4, 5]
[7, 8]
[12]
[10, 11]
[13, 14, 15]
我们可以看到根节点过早分裂了。如何优化我的插入函数以防止这种过早分裂并实现以下结果?
[3, 6, 9, 12]
[1, 2]
[4, 5]
[7, 8]
[10, 11]
[13, 14, 15]
关于如何调整我的 insertNonFull 和 insertKey 函数来实现此目的有什么建议吗?
插入是在叶子节点完成的。如果此操作导致节点满,则将节点分成两个,并提升中间元素。这会级联到树上。
在我看来,文献对实现细节非常模糊,具体来说,它是如何变得“过度”以及如何进行这种“促销”的。我能想到 4 种方法。
log n
额外空间进行插入。hole
之外。这意味着我们可以直接跳到最终的拓扑,并按照我们喜欢的任何顺序进行新节点的分配和升级;具体来说,是从树上下来而不是向上。这也是有利的,因为人们仅移动存储器一次。 (不过,这种情况很多。)选择最后一种方法,在从根下降到叶子时,我们需要跟踪未满的最低高度节点。如果路径完全充满钥匙,则需要增加高度。然后你就知道需要多少个额外的节点来完成插入。
例如,将 9 添加到完整的 3 树中需要 4 个额外的节点来增加高度。最低的未满是新根。回溯到最低的未满节点并重新下降,在现有的满子(子 1)节点、来自最低未满(父)节点的一个条目和新分配的节点之间拆分数据和
O(log n)
(子 2)节点。
现在最低的未满是沿着到新密钥的路径上的前一个密钥的子级。重复。
hole
向前跳到应插入的位置。
hole
节点应该具有键的值,该值最终将被替换。的可视化动画