Balance-Tree-Altered
@@ -569,6 +569,142 @@ deleteKey(node, k):
|
||||
|
||||
---
|
||||
|
||||
✅ 对的,你理解完全正确!我帮你梳理一下:
|
||||
|
||||
---
|
||||
|
||||
### 在 B-树删除时,如果要删除的关键字 `x->keys[i]` 在**内部节点**:
|
||||
|
||||
* **前驱(predecessor)**
|
||||
|
||||
* 就是 `x->children[i]` 这个子树中的最大值。
|
||||
* 为什么?因为 `x->children[i]` 里所有关键字都 **小于** `x->keys[i]`,而前驱就是其中最大的。
|
||||
* 找法:一路往右孩子走直到叶子,拿到最后一个 key。
|
||||
* 伪代码:
|
||||
|
||||
```cpp
|
||||
Node<T>* cur = x->children[i];
|
||||
while (!cur->isLeaf) cur = cur->children.back();
|
||||
T predecessor = cur->keys.back();
|
||||
```
|
||||
|
||||
* **后继(successor)**
|
||||
|
||||
* 就是 `x->children[i+1]` 这个子树中的最小值。
|
||||
* 为什么?因为 `x->children[i+1]` 里所有关键字都 **大于** `x->keys[i]`,而后继就是其中最小的。
|
||||
* 找法:一路往左孩子走直到叶子,拿到第一个 key。
|
||||
* 伪代码:
|
||||
|
||||
```cpp
|
||||
Node<T>* cur = x->children[i+1];
|
||||
while (!cur->isLeaf) cur = cur->children.front();
|
||||
T successor = cur->keys.front();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 删除时的策略
|
||||
|
||||
* 如果前驱所在子树 `x->children[i]` 有 ≥ t 个 key:
|
||||
|
||||
* 用前驱替代 `x->keys[i]`,再递归删除前驱。
|
||||
* 否则如果后继所在子树 `x->children[i+1]` 有 ≥ t 个 key:
|
||||
|
||||
* 用后继替代 `x->keys[i]`,再递归删除后继。
|
||||
* 否则(两个子树都只有 t-1 个 key):
|
||||
|
||||
* 把 `x->keys[i]` 下移到左子树,和右子树合并,再递归删除。
|
||||
|
||||
---
|
||||
|
||||
📘 **直观理解**:
|
||||
`x->keys[i]` 在父节点,它左边整个子树都比它小,右边整个子树都比它大。
|
||||
所以前驱就是左子树里最大值,后继就是右子树里最小值。
|
||||
|
||||
---
|
||||
|
||||
要不要我帮你画一个 **图解例子**(比如 `t=2` 的 B 树,删除一个内部节点的情况),演示「前驱替代」和「后继替代」的区别?
|
||||
|
||||
|
||||
如果你需要,我可以帮你 **写一个完整的 C++ B 树删除函数实现**,直接和你已有的 `BTree` 类整合,并附详细注释。
|
||||
|
||||
你希望我帮你写吗?
|
||||
|
||||
---
|
||||
``` html
|
||||
总结:
|
||||
1、删除
|
||||
2、查看是否下溢出
|
||||
3、下溢出:
|
||||
a.左右节点可以借(大于t - 1)
|
||||
父亲下来,借的成为父亲
|
||||
b.不可以借
|
||||
和兄弟合并
|
||||
总结得很到位 👍,我帮你整理一下,更系统一些,方便你以后复习 B-树删除算法。
|
||||
```
|
||||
---
|
||||
|
||||
## 🌳 B-树删除算法总结
|
||||
|
||||
### 1. 删除目标
|
||||
|
||||
* **在叶子节点找到 key**
|
||||
👉 直接删除,不涉及复杂调整。
|
||||
|
||||
* **在内部节点找到 key**
|
||||
👉 不能直接删,而是通过 **前驱 / 后继替代** 或 **合并** 来保持 B-树平衡。
|
||||
|
||||
---
|
||||
|
||||
### 2. 删除步骤总览
|
||||
|
||||
1. **删除目标 key**
|
||||
|
||||
* 在叶子:直接删。
|
||||
* 在内部:找前驱或后继替换,再递归删除。
|
||||
|
||||
2. **检查是否下溢(underflow)**
|
||||
|
||||
* B-树的每个节点至少要有 `t - 1` 个 key(除了根)。
|
||||
* 如果某个子节点删除后不足 `t - 1`,则发生下溢。
|
||||
|
||||
3. **处理下溢**
|
||||
|
||||
* **a. 向兄弟借关键字(兄弟 ≥ t)**
|
||||
|
||||
* 父节点“下放”一个关键字到下溢的节点,兄弟的关键字“上升”到父节点。
|
||||
* 保证节点恢复到 ≥ `t - 1`。
|
||||
* 📌 这是 **旋转(rotation)** 操作。
|
||||
|
||||
* **b. 无法借(兄弟也是 t - 1) → 合并**
|
||||
|
||||
* 将下溢节点、父节点中一个关键字、兄弟节点 **合并** 成一个节点。
|
||||
* 父节点关键字数减少 1。
|
||||
* 如果父节点变空,整棵树高度减 1(根被替换)。
|
||||
|
||||
---
|
||||
|
||||
### 3. 前驱 / 后继选择
|
||||
|
||||
* **前驱**:`x->keys[i]` 的左子树 `x->children[i]` 中的最大值。
|
||||
* **后继**:`x->keys[i]` 的右子树 `x->children[i+1]` 中的最小值。
|
||||
* 替代规则:
|
||||
|
||||
* 左子树有足够关键字(≥ t),用前驱替代。
|
||||
* 否则若右子树有足够关键字(≥ t),用后继替代。
|
||||
* 否则左右子树都不够,执行合并。
|
||||
|
||||
---
|
||||
|
||||
### 📌 最简总结版(你说的很对)
|
||||
|
||||
1. 删除目标 key。
|
||||
2. 如果节点关键字数 ≥ `t - 1`,不用处理。
|
||||
3. 如果 < `t - 1`(下溢):
|
||||
|
||||
* **能借** → 借兄弟。
|
||||
* **不能借** → 合并兄弟和父节点关键字。
|
||||
|
||||
---
|
||||
|
||||
要不要我帮你画一个小例子(`t=2` 的 B-树),演示 **借** 和 **合并** 两种下溢处理,这样你一眼就能看懂?
|
||||
|
BIN
BinaryTree/BalanceTree/B-Tree/img/erase/1.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
BinaryTree/BalanceTree/B-Tree/img/erase/2.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
BinaryTree/BalanceTree/B-Tree/img/erase/3.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
BinaryTree/BalanceTree/B-Tree/img/erase/4.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
BinaryTree/BalanceTree/B-Tree/img/erase/5.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
BinaryTree/BalanceTree/B-Tree/img/erase/6.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
BinaryTree/BalanceTree/B-Tree/img/insert/1.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
BinaryTree/BalanceTree/B-Tree/img/insert/10.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
BinaryTree/BalanceTree/B-Tree/img/insert/2.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
BinaryTree/BalanceTree/B-Tree/img/insert/3.png
Normal file
After Width: | Height: | Size: 6.5 KiB |
BIN
BinaryTree/BalanceTree/B-Tree/img/insert/4.png
Normal file
After Width: | Height: | Size: 8.8 KiB |
BIN
BinaryTree/BalanceTree/B-Tree/img/insert/5.png
Normal file
After Width: | Height: | Size: 9.4 KiB |
BIN
BinaryTree/BalanceTree/B-Tree/img/insert/6.png
Normal file
After Width: | Height: | Size: 9.9 KiB |
BIN
BinaryTree/BalanceTree/B-Tree/img/insert/7.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
BinaryTree/BalanceTree/B-Tree/img/insert/8.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
BinaryTree/BalanceTree/B-Tree/img/insert/9.png
Normal file
After Width: | Height: | Size: 24 KiB |
@@ -261,3 +261,54 @@
|
||||
|
||||
你希望我画吗?
|
||||
|
||||
---
|
||||
没错 ✅,你总结得非常到位。
|
||||
|
||||
这是二叉搜索树(BST)**删除的基本套路**,红黑树只是额外多了颜色和修复步骤而已。
|
||||
|
||||
---
|
||||
|
||||
### 普通 BST 的删除分类
|
||||
|
||||
假设删除的节点是 `z`:
|
||||
|
||||
1. **没有孩子(叶子)**
|
||||
→ 直接删。
|
||||
|
||||
2. **只有一个孩子**
|
||||
→ 用孩子替换它。
|
||||
|
||||
3. **有两个孩子**
|
||||
→ 找 **中序后继(右子树最小)** 或 **中序前驱(左子树最大)**,
|
||||
|
||||
* 把 `y.key` 复制到 `z.key`;
|
||||
* 实际删除 `y`(而 `y` 一定是“最多一个孩子”的情况)。
|
||||
|
||||
所以 **情况 3 总是能转化为情况 1 或 2**。
|
||||
|
||||
---
|
||||
|
||||
### 为什么后继/前驱最多只有一个孩子?
|
||||
|
||||
比如用 **后继**(右子树最小的节点)来说明:
|
||||
|
||||
* `y` 在 `z.right` 里,且 `y` 是最靠左的。
|
||||
* 所以 `y` 不可能有左孩子(否则就不是最左的了)。
|
||||
* 因此 `y` 要么没有孩子,要么只有右孩子。
|
||||
|
||||
对称地,用前驱的话,也必然是“没有孩子或只有左孩子”。
|
||||
|
||||
---
|
||||
|
||||
### 到红黑树这里
|
||||
|
||||
红黑树继承了 BST 结构,所以删除逻辑一样:
|
||||
|
||||
* 如果 `z` 有两个孩子,**我们实际删的就是它的后继/前驱 `y`**,而不是直接删 `z`。
|
||||
* 这就是你说的“删除下移”。
|
||||
|
||||
真正复杂的地方在于:**如果被删的是黑色节点**,就需要一整套 `fixup` 来维持红黑树的平衡。
|
||||
|
||||
---
|
||||
|
||||
要不要我帮你把“3 种情况 → 转化为 2 种简单情况”的流程画成一个 **决策图(删除流程图)**?这样会更直观。
|
||||
|
BIN
BinaryTree/BalanceTree/Red-Black Tree/info.jpg
Normal file
After Width: | Height: | Size: 132 KiB |