引言
在前面的树系列中,我们学习了二叉搜索树(BST)和 AVL 树。AVL 树通过严格的平衡条件(|BF| ≤ 1)保证 O(log n) 的性能,但代价是删除操作可能触发 O(log n) 次旋转。
红黑树 (Red-Black Tree)采用了一种更"灵活"的平衡策略------用颜色约束 代替高度约束。它在插入和删除时最多只需要 O(1) 次旋转(插入最多 2 次,删除最多 3 次),同时仍能保证树的高度不超过 2log(n+1),整体性能优于 AVL 树。
正因如此,红黑树成为了实际工程中最广泛使用的平衡树------Java 的 TreeMap/TreeSet、C++ 的 std::map/std::set、Linux 内核的 CFS 调度器、epoll 的事件管理......底层都是红黑树。

第一部分:红黑树的五条性质
一、五条性质
红黑树是一棵满足以下五条性质的二叉搜索树:
1、每个节点要么是红色,要么是黑色
2、根节点是黑色
3、每个叶子节点(NIL)是黑色
4、如果一个节点是红色,则它的两个子节点必须是黑色 (即:不允许连续两个红色节点)
5、从任意节点到其所有后代叶子节点的路径上, 包含相同数量的黑色节点
记忆口诀:
根黑叶黑,红不连续,黑高相等
二、性质图解

三、红黑树的高度上界
定理 :一棵含有 n 个节点的红黑树,其高度最多为 2log₂(n+1)。

四、NIL 节点的作用
红黑树通常使用一个哨兵 NIL 节点来代表所有空指针(类似带头结点的链表):

第二部分:节点结构与基本操作
一、节点结构
cpp
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
typedef int ElemType;
// 颜色定义
typedef enum { RED, BLACK } Color;
typedef struct RBNode {
ElemType data;
Color color;
struct RBNode* left;
struct RBNode* right;
struct RBNode* parent; // 父节点(旋转和修复时需要)
} RBNode;
// 全局 NIL 哨兵(所有空指针指向它)
RBNode* NIL;
二、初始化与辅助函数
cpp
// 初始化 NIL 哨兵
void initNIL() {
NIL = (RBNode*)malloc(sizeof(RBNode));
NIL->color = BLACK;
NIL->left = NIL->right = NIL->parent = NULL;
}
// 创建新节点(默认红色)
RBNode* createNode(ElemType val) {
RBNode* node = (RBNode*)malloc(sizeof(RBNode));
node->data = val;
node->color = RED; // 新节点默认红色(更容易修正)
node->left = NIL;
node->right = NIL;
node->parent = NULL;
return node;
}
// 判断节点是否为红色
bool isRed(RBNode* node) {
return node != NIL && node->color == RED;
}
// 判断节点是否为黑色
bool isBlack(RBNode* node) {
return node == NIL || node->color == BLACK;
}
为什么新节点默认是红色?

第三部分:旋转操作
红黑树使用与 AVL 树相同的两种基本旋转(左旋和右旋),区别在于需要维护 parent 指针和颜色。
一、左旋

cpp
void rotateLeft(RBNode** root, RBNode* A) {
RBNode* C = A->right; // C = A 的右子
A->right = C->left; // A 的右子 = C 的左子
if (C->left != NIL) {
C->left->parent = A;
}
C->parent = A->parent; // 继承 A 的父节点
if (A->parent == NULL) {
*root = C; // A 是根,C 成为新根
} else if (A == A->parent->left) {
A->parent->left = C;
} else {
A->parent->right = C;
}
C->left = A; // A 成为 C 的左子
A->parent = C;
}
二、右旋
cpp
void rotateRight(RBNode** root, RBNode* A) {
RBNode* B = A->left; // B = A 的左子
A->left = B->right; // A 的左子 = B 的右子
if (B->right != NIL) {
B->right->parent = A;
}
B->parent = A->parent; // 继承 A 的父节点
if (A->parent == NULL) {
*root = B; // A 是根,B 成为新根
} else if (A == A->parent->left) {
A->parent->left = B;
} else {
A->parent->right = B;
}
B->right = A; // A 成为 B 的右子
A->parent = B;
}
第四部分:插入操作
一、插入流程

二、三种修复情况
插入后需要修复的情况取决于叔叔节点(父节点的兄弟)的颜色:

三、插入修复图解
Case 1:叔叔是红色(只需变色)

Case 2:叔叔黑色 + X 是右子 → 转为 Case 3

Case 3:叔叔黑色 + X 是左子 → 右旋 + 变色

四、插入修复代码
cpp
void insertFixUp(RBNode** root, RBNode* X) {
// 只要父节点是红色(需要修复)
while (isRed(X->parent)) {
RBNode* P = X->parent;
RBNode* G = P->parent; // 祖父一定存在且为黑色
if (P == G->left) { // 父节点是左子
RBNode* U = G->right; // 叔叔是右子
// Case 1:叔叔是红色
if (isRed(U)) {
P->color = BLACK;
U->color = BLACK;
G->color = RED;
X = G; // 上移继续检查
} else {
// Case 2:叔叔黑色,X 是右子(LR → 转 LL)
if (X == P->right) {
rotateLeft(root, P);
X = P;
P = X->parent;
}
// Case 3:叔叔黑色,X 是左子(LL)
P->color = BLACK;
G->color = RED;
rotateRight(root, G);
}
} else { // 父节点是右子(镜像对称)
RBNode* U = G->left;
if (isRed(U)) {
P->color = BLACK;
U->color = BLACK;
G->color = RED;
X = G;
} else {
if (X == P->left) { // RL → 转 RR
rotateRight(root, P);
X = P;
P = X->parent;
}
P->color = BLACK;
G->color = RED;
rotateLeft(root, G);
}
}
}
(*root)->color = BLACK; // 确保根是黑色
}
五、插入主函数
cpp
void rbInsert(RBNode** root, ElemType val) {
// 1. 普通 BST 插入
RBNode* newNode = createNode(val);
RBNode* parent = NULL;
RBNode* cur = *root;
while (cur != NIL && cur != NULL) {
parent = cur;
if (val < cur->data)
cur = cur->left;
else if (val > cur->data)
cur = cur->right;
else
return; // 不允许重复
}
newNode->parent = parent;
if (parent == NULL) {
*root = newNode;
} else if (val < parent->data) {
parent->left = newNode;
} else {
parent->right = newNode;
}
newNode->left = NIL;
newNode->right = NIL;
// 2. 修复红黑树性质
insertFixUp(root, newNode);
}
第五部分:删除操作
一、删除流程
红黑树删除流程
① 像 BST 一样删除节点
② 记录实际被删除节点的颜色
③ 如果被删除的是黑色节点,需要修复
④ 修复从替代节点开始
关键:只有删除黑色节点才会破坏性质⑤
二、四种删除修复情况
修复的核心思想:让替代节点"多携带一层黑色"(双黑),然后通过旋转和变色消除。

三、删除修复代码
cpp
void deleteFixUp(RBNode** root, RBNode* X) {
while (X != *root && isBlack(X)) {
RBNode* P = X->parent;
if (X == P->left) { // X 是左子
RBNode* S = P->right; // 兄弟
// Case 1:兄弟是红色
if (isRed(S)) {
S->color = BLACK;
P->color = RED;
rotateLeft(root, P);
S = P->right; // 新的兄弟
}
// Case 2:兄弟黑色,且两个侄子都是黑色
if (isBlack(S->left) && isBlack(S->right)) {
S->color = RED;
X = P; // 上移
} else {
// Case 3:兄弟黑色,左侄子红,右侄子黑
if (isBlack(S->right)) {
S->left->color = BLACK;
S->color = RED;
rotateRight(root, S);
S = P->right;
}
// Case 4:兄弟黑色,右侄子红
S->color = P->color;
P->color = BLACK;
S->right->color = BLACK;
rotateLeft(root, P);
X = *root; // 修复完成,退出
}
} else { // X 是右子(镜像对称)
RBNode* S = P->left;
if (isRed(S)) {
S->color = BLACK;
P->color = RED;
rotateRight(root, P);
S = P->left;
}
if (isBlack(S->left) && isBlack(S->right)) {
S->color = RED;
X = P;
} else {
if (isBlack(S->left)) {
S->right->color = BLACK;
S->color = RED;
rotateLeft(root, S);
S = P->left;
}
S->color = P->color;
P->color = BLACK;
S->left->color = BLACK;
rotateRight(root, P);
X = *root;
}
}
}
X->color = BLACK;
}
四、删除主函数
cpp
// 用子树 v 替换子树 u
void transplant(RBNode** root, RBNode* u, RBNode* v) {
if (u->parent == NULL) {
*root = v;
} else if (u == u->parent->left) {
u->parent->left = v;
} else {
u->parent->right = v;
}
v->parent = u->parent;
}
// 找最小值节点
RBNode* minimum(RBNode* node) {
while (node->left != NIL) node = node->left;
return node;
}
void rbDelete(RBNode** root, ElemType val) {
RBNode* Z = *root;
while (Z != NIL) {
if (val == Z->data) break;
else if (val < Z->data) Z = Z->left;
else Z = Z->right;
}
if (Z == NIL) return; // 未找到
RBNode* Y = Z;
RBNode* X;
Color Y_original_color = Y->color;
if (Z->left == NIL) {
X = Z->right;
transplant(root, Z, Z->right);
} else if (Z->right == NIL) {
X = Z->left;
transplant(root, Z, Z->left);
} else {
Y = minimum(Z->right);
Y_original_color = Y->color;
X = Y->right;
if (Y->parent == Z) {
X->parent = Y;
} else {
transplant(root, Y, Y->right);
Y->right = Z->right;
Y->right->parent = Y;
}
transplant(root, Z, Y);
Y->left = Z->left;
Y->left->parent = Y;
Y->color = Z->color;
}
free(Z);
if (Y_original_color == BLACK) {
deleteFixUp(root, X);
}
}
第六部分:完整测试
cpp
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
// ... 上面所有代码 ...
void inorder(RBNode* root) {
if (root == NIL) return;
inorder(root->left);
printf("%d(%c) ", root->data, root->color == RED ? 'R' : 'B');
inorder(root->right);
}
int main() {
initNIL();
RBNode* root = NULL;
int values[] = {10, 20, 30, 15, 25, 5, 1};
int n = sizeof(values) / sizeof(values[0]);
printf("===== 插入测试 =====\n");
for (int i = 0; i < n; i++) {
rbInsert(&root, values[i]);
printf("插入 %d:", values[i]);
inorder(root);
printf("\n");
}
printf("\n===== 删除测试 =====\n");
int dels[] = {20, 10, 30};
for (int i = 0; i < 3; i++) {
printf("删除 %d:", dels[i]);
rbDelete(&root, dels[i]);
inorder(root);
printf("\n");
}
return 0;
}
第七部分:红黑树 vs AVL 树
| 对比项 | AVL 树 | 红黑树 |
|---|---|---|
| 平衡策略 | 严格(|BF| ≤ 1) | 宽松(颜色约束) |
| 树高 | 更矮 | 最多高 1 倍 |
| 查找 | 稍快(更矮) | 稍慢 |
| 插入旋转 | 最多 2 次 | 最多 2 次 |
| 删除旋转 | 可能 O(log n) 次 | 最多 3 次 |
| 实现复杂度 | 中等 | 较高 |
| 适用场景 | 查询多 | 增删多 |
实际应用:红黑树应用更广,因为实际系统中增删操作频繁,红黑树的删除优势明显。
总结
一、核心要点
| 要点 | 内容 |
|---|---|
| 五条性质 | 根黑叶黑,红不连续,黑高相等 |
| 新节点颜色 | 默认红色(更容易修正) |
| NIL 哨兵 | 所有空指针指向同一个黑色 NIL |
| 插入修复 | 最多 2 次旋转(Case 1 只变色) |
| 删除修复 | 最多 3 次旋转 |
| 应用 | std::map、TreeMap、epoll、内核 |
二、插入修复记忆口诀

三、一句话记忆
红黑树用五条颜色约束代替 AVL 的高度约束,插入删除最多 O(1) 次旋转。新节点默认红色,通过叔父关系分情况修复,最终保证树高不超过 2log(n+1),是工程界最广泛使用的平衡树。