我正在试图弄清楚当节点溢出时到底发生了什么。 信息: 在我的b +树中,每个块有4个指针和3个数据部分。 问题: 我明白当有溢出时,我会分成两个节点,每个节点都有2个节点 键, 并插入父节点的中间值,而不从儿子中删除(与b树不同)。
然而我遇到了情况:
|21|30|50|
|10|20|-| |21|22|25| |30|40|-| |50|60|80|
我要插入密钥23 首先我分裂| 21 | 22 | 25 |成:| 21 | 22 | - |和| 23 | 25 | - | 现在我需要将键23插入父| 21 | 30 | 50 |巫婆导致另一个分裂。 | 21 | 23 | - |和| 30 | 50 | - | 但是指向30之前的指针在哪里? 是否有可能这个指针和& 23点后的那个| | | 23 | 25 | - | ?
答案 0 :(得分:2)
插入23时:
基本上,该插入将使树的深度增加1
答案 1 :(得分:0)
以下是如何处理指针。这是插入之前的B +树。 (一些用于使指针更容易看到的填充)
[21 30 50]
/ \ \ \
[10|20|-] => [21|22|25] => [30|40|-] => [50|60|80]
插入23后,您将拥有2个节点。重要的是要知道左侧拆分应始终为同一实例,并且右侧拆分应为新节点。这将使处理指针更容易一些。
所以分裂应该是这样的。
old fresh
[21|22|-] => [23|25|-]
由于左节点是同一个实例,因此root的键21
具有正确的右指针。以下是在分割根之前和分割叶之后节点的样子。我们处于流程的中间。
[21 30 50]
/ \ \ \
[10|20|-] => [21|22|-] => [23|25|-] => [30|40|-] => [50|60|80]
只应在根目录23
旁边添加一个键21
的新项。由于root没有空间,它也会分裂。 中键是23 (右侧分割的第一项)。
[21 30] [ 50 ]
/ \ \ * \
[10|20|-] => [21|22|-] => [23|25|-] => [30|40|-] => [50|60|80]
由于root被拆分,我们必须将中间键添加到左侧或右侧分割,并找到要升级的新中键。注意右侧分割时的空指针。因为该节点是新鲜的,所以必须尽快初始化!
( root从右边拆分,意味着如果项目数量很少,则在右边节点中放置的项目较少。但一般来说,拆分的方式并不重要。)< / p>
我们的中键是23.所以我们加23。
[21 23 30] [ 50 ]
/ \ \ \ * \
[10|20|-] => [21|22|-] => [23|25|-] => [30|40|-] => [50|60|80]
好!如果这里没有拆分,我们的插入现在就完成了。但由于在这个层面存在分裂,我们必须找到新的中间关键来促进。也不要忘记新节点的空指针。
(如果分割叶子,你必须在叶子上保留键值并提升中键的副本,但是如果分割内部节点,你可以安全地移动和提升键。)< / p>
此处新中键为30 。让我们从左侧节点弹出它。
30
\
[30|40|-]
(重要的是如何选择中间键。始终是从底部分割中获取中间键的节点,应该删除一个项目以获得新的中间键。如果该节点是左侧节点,则删除最后一个来自它的项目,否则放弃第一项。)
[21 23] 30 [ 50 ]
/ \ \ \ * \
[10|20|-] => [21|22|-] => [23|25|-] => [30|40|-] => [50|60|80]
请注意,30不在任何拆分中。这是我们必须处理的中间键。 始终将中间键的右侧节点放在新节点的左侧。
[21 23] 30 [50]
/ \ \ * / \
[10|20|-] => [21|22|-] => [23|25|-] => [30|40|-] => [50|60|80]
然后中键的右指针将指向右侧分割的新节点。最后提升中键,创建左侧左侧分割,右侧右侧分割和中间键键的新根。
[ 30 ]
/ \
[21 23] [50]
/ \ \ / \
[10|20|-] => [21|22|-] => [23|25|-] => [30|40|-] => [50|60|80]
恭喜!你完成了插入。它在纸面上看起来很容易,但在编码时我不得不承认它有点挑战。
有几种方法可以在内存中表示这些key-node
项。我的方法是每个key-node
项都有一个带键的右指针。所以每个内部节点都有key-node
个数组。这样,键和节点指针总是保持在一起。最左边的子节点由一个单独的左指针处理,这个指针保存在每个内部节点上,并且永远不为空(在完成操作之后)。
答案 2 :(得分:0)
在B +树中,叶子和非叶子节点具有不同的结构。因此,它们的溢出机制也不同。您说的对叶节点溢出是正确的(无需删除子节点的父密钥即可完成)。但是,当非叶子节点溢出并被拆分时,子节点将不保留父密钥(父子密钥将从子子擦除)。
答案 3 :(得分:-1)
您必须了解发生的问题是因为您使用的表示模型不正确。您应该有一个与指针关联的数据值,以及数据值是指针引用的子树中最小值的不变量。
所以这就是你应该如何在插入前表示b树节点。
10|21|30|50| <--- root node
10|20| 21|22|25| 30|40| 50|60|80| <--- leaf nodes
在此表示形式中,根节点中值10之后的指针指向具有第一个值10等的叶节点。
当您插入23时,它将插入到叶节点中,其第一个值为21.这将生成以下树,无需拆分。
10|21|30|50| <--- root node
10|20| 21|22|23|25| 30|40| 50|60|80| <--- leaf nodes
当插入产生你所描述的效果的24时,你得到的是以下
10|30| <--- new root node
10|21|24| 30|50| <--- intermediate nodes
10|20| 21|22|23| 24|25| 30|40| 50|60|80| <--- leaf nodes
如你所见,不再存在歧义。叶节点被分割,并且键指针对24 |必须插入根节点。因为它已满,所以必须分开。现在有5个值,您将获得一个具有3个值的节点和一个具有2个节点的节点。您可以自由选择左侧节点还是右侧节点获取三个值。重要的是保留了基本不变量。节点中的每个键值是其关联指针引用的子树中的最小元素。必须添加一个新的根节点,其键值指针集现在应该是显而易见的。没有歧义。
这也很明显,许多优化策略都是可能的。我们可以将值21移动到未满的左叶节点,而不是拆分根节点。具有第一个值10的那个。这避免了分割根节点并保持b树高度不变并且产生更好的b树填充的需要。所以你最小化空间和搜索时间。但这意味着可以有效地检查横向平衡是否可行。仍然需要更改根节点中的键值。等
如您所见,b-tree中的值10在任何叶子节点中都不是真正需要的,并且在b-tree表示中经常被省略(即wikipedia)但这可能会让人感到困惑并且很可能你在哪里的原因。 :)
答案 4 :(得分:-1)
从我所教过的,最后一个节点可以有一个小于n / 2的节点。因此,在您的示例中,顶部节点将变为:
|23|
/ \
|21|22|-| |25|-|-|
我认为这有点道理。如果你考虑一下,你有5个离开节点,所以你需要从上层开始有5个指针。这种安排是唯一的方法,你可以有5个指针,所有其他组合将溢出节点或创建额外的指针。
如果节点是| 21 | 22 | - |和| 23 | 25 | - |,然后根节点将是| 23 | - | - |。那么,在右边节点中有23个是没有意义的,因为正确节点中的任何东西都必须等于或大于23!