红黑树的规则
红黑树通过以下 5条规则 保持平衡:
- 颜色规则:每个节点是 红色 或 黑色。
- 根节点规则:根节点必须是 黑色。
- 叶子节点规则:叶子节点(NIL 节点,空节点)是 黑色。
- 红色节点规则:红色节点的子节点必须是 黑色(即不能有连续的红色节点)。
- 黑色高度规则:从任意节点到其所有叶子节点的路径上,黑色节点的数量相同(称为 黑高)。
红黑树的插入与修复
当插入一个新节点时,红黑树可能违反规则,需要通过 颜色调整 和 旋转 来修复。
4.1 插入步骤
- 按BST规则插入:新节点初始为 红色(减少对黑高的影响)。
- 修复颜色冲突:如果父节点是红色,违反规则4,需要调整。
4.2 修复操作的3种情况
- Case 1:叔叔节点是红色。
- 将父节点和叔叔节点变为黑色,祖父节点变为红色。
- Case 2:叔叔节点是黑色,且新节点是父节点的右子节点。
- 对父节点左旋,转为 Case 3。
- Case 3:叔叔节点是黑色,且新节点是父节点的左子节点。
- 将父节点变黑,祖父节点变红,然后对祖父节点右旋。
举例:
以下是向 `std::map`(底层红黑树)中插入节点的详细过程示例。假设我们要依次插入键 `10`、`20`、`30`、`5`、`15`,并逐步展示红黑树的调整过程。
---
### **插入步骤详解**
#### **1. 插入第一个节点 `10`**
• **规则**:根节点必须是黑色。
• **操作**:直接插入为根节点,颜色设为黑色。
• **树结构**:
```plaintext
10(B)
/ \
NIL NIL
```
---
#### **2. 插入第二个节点 `20`**
• **插入规则**:新节点初始为红色。
• **操作**:`20` 作为 `10` 的右子节点插入,颜色为红色。
• **检查冲突**:父节点 `10` 是黑色,没有违反规则。
• **树结构**:
```plaintext
10(B)
/ \
NIL 20(R)
/ \
NIL NIL
```
---
#### **3. 插入第三个节点 `30`**
• **插入位置**:`30` 作为 `20` 的右子节点插入,颜色为红色。
• **冲突检查**:
• 父节点 `20(R)` 是红色,违反规则4(红色节点的子节点必须为黑色)。
• 叔叔节点是 `NIL`(黑色,属于规则3的叶子节点)。
• **修复操作**:进入 **Case 2 → Case 3**。
##### **Case 2:叔叔节点是黑色,新节点是父节点的右子节点**
• **操作**:对父节点 `20(R)` 左旋。
• **旋转后结构**:
```plaintext
10(B)
\
30(R)
/
20(R)
```
##### **Case 3:叔叔节点是黑色,新节点是父节点的左子节点**
• **操作**:
1. 将父节点 `30(R)` 变为黑色。
2. 将祖父节点 `10(B)` 变为红色。
3. 对祖父节点 `10(B)` 右旋。
• **最终结构**:
```plaintext
20(B)
/ \
10(R) 30(R)
```
---
#### **4. 插入第四个节点 `5`**
• **插入位置**:`5` 作为 `10(R)` 的左子节点插入,颜色为红色。
• **冲突检查**:
• 父节点 `10(R)` 是红色。
• 叔叔节点是 `30(R)`(红色)。
• **修复操作**:进入 **Case 1**。
##### **Case 1:叔叔节点是红色**
• **操作**:
1. 将父节点 `10(R)` 和叔叔节点 `30(R)` 变为黑色。
2. 将祖父节点 `20(B)` 变为红色。
• **最终结构**:
```plaintext
20(R)
/ \
10(B) 30(B)
/
5(R)
```
---
#### **5. 插入第五个节点 `15`**
• **插入位置**:`15` 作为 `10(B)` 的右子节点插入,颜色为红色。
• **冲突检查**:
• 父节点 `10(B)` 是黑色,没有冲突。
• **树结构**:
```plaintext
20(R)
/ \
10(B) 30(B)
/ \
5(R) 15(R)
```
---
### **最终红黑树验证**
1. **根节点为黑色**:当前根节点 `20` 是红色(违反规则2)→ **需要修复**。
• **修复**:将根节点强制设为黑色。
```plaintext
20(B)
/ \
10(R) 30(B)
/ \
5(R) 15(R)
```
2. **红色节点规则**:
• `10(R)` 的子节点 `5(R)` 和 `15(R)` 违反规则4(红色节点不能有红色子节点)→ **需要修复**。
3. **修复 `10(R)` 的子节点冲突**:
• 选择 `5(R)` 或 `15(R)` 进行修复,以 `5(R)` 为例:
◦ 父节点 `10(R)` 是红色,叔叔节点 `30(B)` 是黑色。
◦ 进入 **Case 2 → Case 3**。
◦ **最终结构**:
```plaintext
20(B)
/ \
15(B) 30(B)
/
10(R)
\
5(R)
```
---
### **关键点总结**
1. **插入新节点**:初始为红色,按BST规则插入。
2. **冲突修复**:
• **Case 1**:叔叔节点是红色 → 颜色翻转。
• **Case 2**:叔叔节点是黑色,且新节点是右子节点 → 左旋。
• **Case 3**:叔叔节点是黑色,且新节点是左子节点 → 右旋 + 颜色调整。
3. **根节点强制黑色**:最终确保根节点为黑色。
通过上述步骤,红黑树始终保持平衡,确保 `std::map` 的插入、删除和查找操作高效(`O(log n)`)。