文章目录
-
- [1. 什么是红黑树?](#1. 什么是红黑树?)
- [2. 为什么需要红黑树?](#2. 为什么需要红黑树?)
-
- [2.1 解决 BST 的退化问题](#2.1 解决 BST 的退化问题)
- [2.2 对比 AVL 树(绝对平衡树)](#2.2 对比 AVL 树(绝对平衡树))
- [3. 核心操作:变色与旋转](#3. 核心操作:变色与旋转)
-
- [3.1 变色 (Recoloring)](#3.1 变色 (Recoloring))
- [3.2 左旋 (Left Rotation)](#3.2 左旋 (Left Rotation))
- [3.3 右旋 (Right Rotation)](#3.3 右旋 (Right Rotation))
- [4. 插入情景深度剖析](#4. 插入情景深度剖析)
-
- [情景 1:红黑树为空](#情景 1:红黑树为空)
- [情景 2:父节点 P 是黑色](#情景 2:父节点 P 是黑色)
- [情景 3:父节点 P 是红色(冲突出现!)](#情景 3:父节点 P 是红色(冲突出现!))
-
- [3.1 叔叔 U 是红色](#3.1 叔叔 U 是红色)
- [3.2 叔叔 U 是黑色(或 NIL)](#3.2 叔叔 U 是黑色(或 NIL))
- [5. 总结](#5. 总结)
在数据结构的世界里,二叉查找树(BST)是高效搜索的基石。然而,普通的 BST 在极端情况下会退化成"链表",导致性能崩塌。为了解决这个问题,红黑树 应运而生。它不是追求"绝对完美"的平衡,而是通过一种巧妙的颜色约束,实现了"近似平衡",从而在插入、删除和查找之间找到了完美的折中点。
1. 什么是红黑树?
红黑树是一种自平衡的二叉查找树(Self-balancing Binary Search Tree)。它在每个节点上增加了一个存储位表示节点的颜色,可以是红色(Red)或黑色(Black)。
五大核心规则(红黑规则)
红黑树必须满足以下 5 条性质,缺一不可:
- 节点颜色:每个节点要么是红色,要么是黑色。
- 根节点 :根节点必须是黑色。
- 叶子节点 :所有的叶子节点(NIL 节点,即空节点)都是黑色的。
- 红节点约束 :如果一个节点是红色的,则它的两个子节点必须是黑色的。(这意味着不能有两个连续的红色节点)。
- 黑高一致:从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
10
5
20
3
8
15
25
NIL
NIL
NIL
NIL
上图展示了一颗合法的红黑树。注意:红色节点不相邻,且任意路径上的黑色节点数量相同。
2. 为什么需要红黑树?
2.1 解决 BST 的退化问题
普通的二叉查找树在插入有序数据(如 1, 2, 3, 4, 5)时,会退化成一条斜线(链表),时间复杂度从 O ( log N ) O(\log N) O(logN) 变为 O ( N ) O(N) O(N)。
2.2 对比 AVL 树(绝对平衡树)
- AVL 树:追求极致的平衡(左右子树高度差不超过 1)。查询效率极高,但为了维护这种严格的平衡,插入和删除时需要频繁旋转,开销较大。
- 红黑树 :追求大致平衡 。通过红黑规则,保证最长路径不超过最短路径的 2 倍。
- 结论 :红黑树在插入、删除频繁的场景下,综合性能优于 AVL 树。这也正是 Java HashMap 选择红黑树的原因。
3. 核心操作:变色与旋转
当插入或删除节点后,红黑树的规则可能会被打破。为了恢复平衡,我们需要用到三种"魔法":变色 、左旋 、右旋。
3.1 变色 (Recoloring)
最简单的调整方式。直接将红色变为黑色,或将黑色变为红色。
3.2 左旋 (Left Rotation)
逆时针旋转。将右子节点变为"父节点",当前节点变为其"左子节点"。
左旋后: S 上位
S
E
c
a
b
左旋前: 以 E 为轴
E
a
S
b
c
3.3 右旋 (Right Rotation)
顺时针旋转。将左子节点变为"父节点",当前节点变为其"右子节点"。
右旋后: E 上位
E
b
S
c
a
右旋前: 以 S 为轴
S
E
a
b
c
4. 插入情景深度剖析
默认原则:新插入的节点总是红色的。
为什么? 因为插入红色节点只可能违反"红红不相邻"规则(规则 4),破坏性较小;而插入黑色节点一定会改变路径上的黑色节点数量(规则 5),破坏性极大,调整极难。
假设新插入节点为 N (New),父节点为 P (Parent),叔叔节点为 U (Uncle),祖父节点为 G (Grandparent)。
情景 1:红黑树为空
- 操作:直接插入,将 N 变为黑色(根节点必须为黑)。
情景 2:父节点 P 是黑色
- 操作:无需调整。插入红色节点不影响黑高,也不违反红红规则。
情景 3:父节点 P 是红色(冲突出现!)
此时必然违反了"红红不相邻"规则。我们需要根据叔叔节点 U 的颜色来决定策略。
3.1 叔叔 U 是红色
- 策略 :变色 + 向上递归
- 操作 :
- 将 P 和 U 变为黑色。
- 将 G 变为红色。
- 将 G 视为新插入的节点,递归处理(因为 G 变红后可能与其父节点冲突)。
调整后: P黑, U黑, G红
G
P
U
N
调整前: P红, U红
G
P
U
N
3.2 叔叔 U 是黑色(或 NIL)
此时需要旋转。根据 N 在 P 的位置(左/右)以及 P 在 G 的位置(左/右),分为四种情况(LL, RR, LR, RL)。
以 LL 型为例(P 是 G 的左子,N 是 P 的左子):
- 策略 :变色 + 右旋
- 操作 :
- 将 P 变为黑色。
- 将 G 变为红色。
- 对 G 进行右旋。
调整后: P变黑, G变红, 右旋
P
N
G
U
调整前: P红, U黑, LL型
G
P
U
N
5. 总结
红黑树的魅力在于它通过复杂的规则,换取了简洁的性能保证。
| 维度 | 普通 BST | AVL 树 | 红黑树 |
|---|---|---|---|
| 平衡性 | 无保障,可能退化 | 严格平衡 | 近似平衡 |
| 查找速度 | O ( N ) O(N) O(N) (最差) | O ( log N ) O(\log N) O(logN) (最快) | O ( log N ) O(\log N) O(logN) |
| 插入/删除 | 快,但容易失衡 | 慢 (频繁旋转) | 较快 (旋转少,变色多) |
| 适用场景 | 简单数据 | 读多写少 | 综合场景 (如 HashMap) |
理解红黑树,不仅仅是背诵规则,更是理解 Trade-off(权衡) 的艺术------在严格与混乱之间,寻找最高效的中间态。