红黑树(Red-Black Tree)
红黑树是一种自平衡二叉搜索树(Self-balancing Binary Search Tree) ,由 Rudolf Bayer 于 1972 年发明。它通过在节点上增加一个"颜色"属性(红或黑),并在插入和删除时遵循特定规则,确保树始终保持近似平衡,从而保证查找、插入、删除操作的时间复杂度都是 O(log n)。
一、五条核心性质(必须同时满足)
性质1:每个节点要么是红色,要么是黑色
性质2:根节点必须是黑色
性质3:所有叶子节点(NIL/空节点)都是黑色
性质4:红色节点的两个子节点必须是黑色(不能有两个连续的红节点)
性质5:从任一节点到其每个叶子节点的所有简单路径上,黑色节点数量相同(黑高相同)
性质5 是红黑树的灵魂------它保证了最长路径不超过最短路径的 2 倍,从而确保树不会严重倾斜。
二、为什么需要红黑树
普通二叉搜索树的问题
10 10
/ \
5 15
/ \
3 20
/ \
1 25
/
22
左图:平衡BST,查找 O(log n) 右图:退化成链表,查找 O(n)
如果按 1, 3, 5, 10, 15, 20, 25 顺序插入,普通 BST 会退化成链表,查找效率变成 O(n)。
红黑树的平衡效果
红黑树通过旋转 + 变色 在插入/删除时自动调整,始终保持近似平衡:
[10B] [10B]
/ \ / \
[5R] [15R] [5B] [20B]
/ \ / \ / \ / \
[3B] [7B][13B] [20B] [3R] [7R][15R] [25R]
三、自平衡机制
1. 插入操作
新节点总是以红色 插入(如果插黑色会破坏性质5的黑高平衡)。
插入后可能违反性质4(出现连续红节点),需要修复:
| 情况 | 修复方式 |
|---|---|
| 叔叔节点是红色 | 父、叔变黑,祖父变红,向上递归 |
| 叔叔是黑色,且新节点是"内侧" | 对父节点旋转,转化为外侧情况 |
| 叔叔是黑色,且新节点是"外侧" | 对祖父节点旋转,父、祖父变色 |
示例:插入 4 到以下树中
[7B] [7B] [7B] [5B]
/ \ / \ / \ / \
[3B] [10B] → [3R] [10B] → [5B] [10B] → [3R] [7R]
/ / \ / \ /
[1B] [1B] [4R] [3B] [4R] [1B]
2. 删除操作
删除更复杂,核心思路是:
- 如果删除的是红节点,直接删,不影响黑高
- 如果删除的是黑节点,会导致该路径黑高减1,需要通过旋转 + 变色 + 兄弟节点借调来恢复平衡
四、时间复杂度
| 操作 | 平均 | 最坏 |
|---|---|---|
| 查找 | O(log n) | O(log n) |
| 插入 | O(log n) | O(log n) |
| 删除 | O(log n) | O(log n) |
为什么最坏也是 O(log n)?
由性质5可知:从根到叶子的最短路径全是黑节点,最长路径是红黑交替。
如果树高为 h,最短路径黑节点数 ≥ h/2。
设黑高为 bh,则 n ≥ 2^bh - 1,因此 h ≤ 2log(n+1)。
所以树高最多是 2log(n),查找最多走 2log(n) 步。
五、实际应用场景
红黑树是许多系统核心数据结构的基石:
| 应用 | 说明 |
|---|---|
Java TreeMap / TreeSet |
基于红黑树实现的有序映射/集合 |
Java HashMap(JDK 8+) |
链表长度 > 8 时转为红黑树,优化哈希冲突 |
| Linux 内核 | 完全公平调度器(CFS)使用红黑树管理进程 |
C++ std::map / std::set |
大多数实现(如 GCC)使用红黑树 |
| Epoll / Nginx | 管理大量文件描述符的事件 |
| 数据库索引 | 部分内存数据库使用红黑树作为索引结构 |
六、红黑树 vs AVL 树
| 对比项 | 红黑树 | AVL 树 |
|---|---|---|
| 平衡度 | 近似平衡(高度 ≤ 2log n) | 严格平衡(高度 ≤ 1.44log n) |
| 查找速度 | 稍慢(树更高) | 更快(树更矮) |
| 插入/删除调整 | 旋转次数少(O(1) 次旋转) | 旋转次数多(O(log n) 次旋转) |
| 适用场景 | 插入删除频繁 | 查找频繁、数据静态 |
| 实现复杂度 | 较复杂 | 更复杂 |
选型建议:
- 数据变动频繁(如任务调度、实时系统)→ 红黑树
- 数据基本不变,查询极多(如字典、配置表)→ AVL 树 或哈希表
七、总结
红黑树是一种通过"红节点不能相邻"和"黑高相同"两条规则来约束树高的自平衡二叉搜索树。它牺牲了一定的绝对平衡性,换取了更少的旋转次数,使得插入和删除操作更高效,成为工程实践中应用最广泛的平衡树。