一、核心定义
红黑树(Red-Black Tree)是一种近似平衡的二叉搜索树(BST) ,通过给节点添加「颜色」属性并遵守严格的颜色规则,保证任意节点到其所有叶子节点的「黑色节点路径长度一致」,从而将树的高度控制在 2log₂(n+1) 以内,确保查询、插入、删除的时间复杂度稳定在 O(log n)。
它是工业界应用最广泛的平衡二叉树(如 Java 的 TreeMap/TreeSet、Linux 内核、HashMap 链表转树),核心优势是「插入/删除时旋转次数少」,适合频繁修改的场景。
资料:https://pan.quark.cn/s/43d906ddfa1b、https://pan.quark.cn/s/90ad8fba8347、https://pan.quark.cn/s/d9d72152d3cf
二、红黑树的5条核心规则(必须满足)
- 颜色规则:每个节点要么是红色,要么是黑色;
- 根节点规则:根节点必须是黑色;
- 叶子节点规则:所有「NIL 叶子节点」(空节点)是黑色(注:实现中通常省略 NIL 节点,默认空指针为黑色叶子);
- 红色节点规则:红色节点的子节点必须是黑色(即「红节点不相邻」,杜绝连续红节点);
- 路径规则:从任意节点到其所有叶子节点的路径中,黑色节点的数量相同(称为「黑高」)。
关键概念:黑高(Black Height)
- 定义:从某节点到叶子节点的路径中,黑色节点的数量(不含当前节点);
- 约束:红黑树的黑高决定了树的平衡程度,任意路径的总长度 ≤ 2×黑高(因为红节点不连续)。
三、核心特性
- 继承BST特性:左子树所有节点值 < 根节点值,右子树所有节点值 > 根节点值,中序遍历为有序序列;
- 近似平衡:树的高度 ≤ 2log₂(n+1)(最坏情况),远优于退化为链表的 BST(O(n));
- 修改效率高:插入最多旋转 2 次,删除最多旋转 3 次,远少于 AVL 树(删除可能多次旋转);
- 空间开销低:仅需为每个节点存储 1bit 的颜色信息(红/黑),比 AVL 树的「高度字段」(int 型)更节省空间。
四、插入操作(核心流程)
插入是红黑树最核心的操作,遵循「先按 BST 插入 → 标记为红色 → 修复颜色规则」的逻辑(标记为红色可最小化破坏规则)。
1. 插入前准备
- 新节点默认标记为红色(若标记为黑色,会直接破坏「路径黑节点数量一致」规则;标记为红色仅可能破坏「红节点不相邻」规则);
- 插入位置为 BST 的叶子节点位置(替换 NIL 叶子)。
2. 失衡场景与修复(按优先级处理)
设:新节点为 Z,父节点为 P,祖父节点为 G,叔叔节点为 U(G 的另一个子节点)。
| 场景 | 条件 | 修复策略 |
|---|---|---|
| 场景1 | 父节点 P 是黑色 |
无需修复(所有规则满足) |
| 场景2 | 父节点 P 是红色,叔叔 U 是红色 |
1. 将 P、U 改为黑色; 2. 将 G 改为红色; 3. 以 G 为新节点递归检查(可能触发上层失衡) |
| 场景3 | 父节点 P 是红色,叔叔 U 是黑色,且 Z 是 P 的右子树(LR型) |
1. 对 P 做左旋转,将 Z 变为 P 的父节点; 2. 转化为场景4 |
| 场景4 | 父节点 P 是红色,叔叔 U 是黑色,且 Z 是 P 的左子树(LL型) |
1. 将 P 改为黑色,G 改为红色; 2. 对 G 做右旋转; 3. 修复完成(无需递归) |
补充:若父节点
P是祖父G的右子树,对应镜像场景(RR型/RL型),修复策略为「场景3→旋转方向相反,场景4→旋转方向相反」。
3. 插入修复示例(LL型)
初始树(G为根,黑色):
G(B)
/
P(R)
/
Z(R) (新插入)
修复步骤:
1. P 改为黑色,G 改为红色:
G(R)
/
P(B)
/
Z(R)
2. 对 G 右旋转:
P(B)
/ \
Z(R) G(R)
3. 根节点强制改为黑色(最终):
P(B)
/ \
Z(R) G(B)
五、删除操作(核心逻辑)
删除是红黑树最复杂的操作,核心是「先按 BST 删除 → 修复被破坏的颜色规则」,难点在于处理「黑色节点被删除后导致的黑高失衡」。
1. 核心前提
- 若删除的是红色节点:直接删除,不破坏任何规则(无需修复);
- 若删除的是黑色节点:会导致该路径的黑高减少 1,触发「双重黑」修复(给删除位置标记「额外黑」,通过调整颜色/旋转消除)。
2. 修复核心思路
设:被删除黑色节点的子节点为 X(携带「双重黑」标记),兄弟节点为 S,父节点为 P。
| 核心场景 | 修复策略 |
|---|---|
| 场景1 | X 是根节点 |
| 场景2 | S 是红色 |
| 场景3 | S 是黑色,且 S 的两个子节点都是黑色 |
| 场景4 | S 是黑色,S 的左子节点红色、右子节点黑色(RL型) |
| 场景5 | S 是黑色,S 的右子节点红色(RR型) |
注:若
X是父节点的右子树,对应镜像场景(LL型/LR型),旋转方向相反。
六、红黑树 vs AVL树(面试高频对比)
| 对比维度 | 红黑树 | AVL树 |
|---|---|---|
| 平衡标准 | 近似平衡(黑高一致) | 严格平衡( |
| 树高上限 | 2log₂(n+1) | log₂n + 1 |
| 旋转次数 | 插入最多2次,删除最多3次 | 插入最多2次,删除可能O(log n)次 |
| 空间开销 | 1bit/节点(颜色) | 4字节/节点(高度) |
| 查询效率 | 略低(树高稍高) | 更高(树高更矮) |
| 修改效率 | 更高(旋转少) | 更低(旋转多) |
| 适用场景 | 插入/删除频繁(如HashMap、TreeSet) | 查询频繁(如数据库索引辅助结构) |
| 工业应用 | 极广泛(Java、Linux、Redis) | 较少(仅部分小众场景) |
七、代码实现示例(Java:红黑树核心逻辑)
java
// 红黑树节点颜色枚举
enum Color {
RED, BLACK
}
// 红黑树节点定义
class RBNode {
int val;
Color color;
RBNode left, right, parent; // 父节点(红黑树必须维护)
public RBNode(int val) {
this.val = val;
this.color = Color.RED; // 新节点默认红色
this.left = this.right = this.parent = null;
}
}
// 红黑树核心实现
class RBTree {
private RBNode root;
private final RBNode NIL; // 哨兵节点(代替空指针,统一处理叶子节点)
public RBTree() {
NIL = new RBNode(-1);
NIL.color = Color.BLACK;
root = NIL;
}
// 左旋转
private void leftRotate(RBNode x) {
RBNode y = x.right; // y是x的右子树
x.right = y.left; // 将y的左子树设为x的右子树
if (y.left != NIL) {
y.left.parent = x;
}
y.parent = x.parent; // y的父节点改为x的父节点
if (x.parent == NIL) { // x是根节点
root = y;
} else if (x == x.parent.left) { // x是左子树
x.parent.left = y;
} else { // x是右子树
x.parent.right = y;
}
y.left = x; // x设为y的左子树
x.parent = y;
}
// 右旋转
private void rightRotate(RBNode y) {
RBNode x = y.left; // x是y的左子树
y.left = x.right; // 将x的右子树设为y的左子树
if (x.right != NIL) {
x.right.parent = y;
}
x.parent = y.parent; // x的父节点改为y的父节点
if (y.parent == NIL) { // y是根节点
root = x;
} else if (y == y.parent.right) { // y是右子树
y.parent.right = x;
} else { // y是左子树
y.parent.left = x;
}
x.right = y; // y设为x的右子树
y.parent = x;
}
// 插入后修复红黑树规则
private void insertFixup(RBNode z) {
while (z.parent.color == Color.RED) { // 父节点是红色才需要修复
if (z.parent == z.parent.parent.left) { // 父节点是祖父的左子树
RBNode u = z.parent.parent.right; // 叔叔节点
// 场景2:叔叔是红色
if (u.color == Color.RED) {
z.parent.color = Color.BLACK;
u.color = Color.BLACK;
z.parent.parent.color = Color.RED;
z = z.parent.parent; // 递归检查祖父
} else {
// 场景3:叔叔是黑色,z是右子树(LR型)
if (z == z.parent.right) {
z = z.parent;
leftRotate(z);
}
// 场景4:叔叔是黑色,z是左子树(LL型)
z.parent.color = Color.BLACK;
z.parent.parent.color = Color.RED;
rightRotate(z.parent.parent);
}
} else { // 父节点是祖父的右子树(镜像场景)
RBNode u = z.parent.parent.left; // 叔叔节点
// 场景2:叔叔是红色
if (u.color == Color.RED) {
z.parent.color = Color.BLACK;
u.color = Color.BLACK;
z.parent.parent.color = Color.RED;
z = z.parent.parent;
} else {
// 场景3:叔叔是黑色,z是左子树(RL型)
if (z == z.parent.left) {
z = z.parent;
rightRotate(z);
}
// 场景4:叔叔是黑色,z是右子树(RR型)
z.parent.color = Color.BLACK;
z.parent.parent.color = Color.RED;
leftRotate(z.parent.parent);
}
}
}
root.color = Color.BLACK; // 根节点强制设为黑色
}
// 插入节点(BST插入 + 修复)
public void insert(int val) {
RBNode z = new RBNode(val);
RBNode y = NIL; // 记录父节点
RBNode x = root;
// 1. 按BST规则找到插入位置
while (x != NIL) {
y = x;
if (z.val < x.val) {
x = x.left;
} else if (z.val > x.val) {
x = x.right;
} else {
return; // 不允许重复值
}
}
z.parent = y;
// 2. 挂载新节点
if (y == NIL) {
root = z; // 树为空,新节点为根
} else if (z.val < y.val) {
y.left = z;
} else {
y.right = z;
}
// 3. 初始化叶子节点(哨兵)
z.left = NIL;
z.right = NIL;
// 4. 修复红黑树规则
insertFixup(z);
}
// 中序遍历(验证有序性)
public void inorder(RBNode node) {
if (node != NIL) {
inorder(node.left);
System.out.print(node.val + "(" + (node.color == Color.RED ? "R" : "B") + ") ");
inorder(node.right);
}
}
// 获取根节点(供遍历调用)
public RBNode getRoot() {
return root;
}
}
// 测试代码
public class RBTreeTest {
public static void main(String[] args) {
RBTree rbTree = new RBTree();
int[] nums = {10, 20, 30, 15, 25, 5};
for (int num : nums) {
rbTree.insert(num);
}
// 中序遍历输出:5(R) 10(B) 15(R) 20(B) 25(R) 30(B)
rbTree.inorder(rbTree.getRoot());
}
}