一、红黑树的定义
1.1 五大性质
红黑树是一种自平衡二叉查找树,每个节点增加一个颜色属性(红或黑),必须满足:
| 性质 | 说明 |
|---|---|
| 性质1 | 每个节点是红色或黑色 |
| 性质2 | 根节点是黑色 |
| 性质3 | 所有叶子节点(NIL)是黑色 |
| 性质4 | 红色节点的两个子节点都是黑色(不能有连续红) |
| 性质5 | 从任一节点到其每个叶子节点的所有路径都包含相同数目的黑色节点(黑高相同) |
关键推论:红黑树的最长路径不超过最短路径的2倍,因此大致平衡。
1.2 节点结构
c
#define RED 0
#define BLACK 1
typedef struct RBNode {
int data;
int color; // RED 或 BLACK
struct RBNode *left;
struct RBNode *right;
struct RBNode *parent; // 需要父指针
} RBNode, *RBTree;
二、红黑树与AVL树的对比
| 对比项 | AVL树 | 红黑树 |
|---|---|---|
| 平衡性 | 严格(高度差≤1) | 大致(黑高相同) |
| 树高 | 约 1.44 log n | 约 2 log n |
| 插入旋转 | 最多2次 | 最多3次 |
| 删除旋转 | 最多O(log n)次 | 最多3次 |
| 查找效率 | 更快 | 略慢 |
| 插入/删除效率 | 较慢 | 更快 |
| 实现复杂度 | 中等 | 较难 |
| 工程应用 | 较少 | 广泛 |
结论:查找多选AVL,插入删除多选红黑树。
三、插入操作的核心规则
3.1 插入规则
新插入的节点默认为红色(不破坏黑高)。
需要调整的情况(新节点N,父节点P,祖父G,叔父U):
| 情况 | 条件 | 处理 |
|---|---|---|
| 情况1 | 空树 | 插入根节点,改为黑色 |
| 情况2 | P为黑色 | 无需调整 |
| 情况3 | P为红色,U为红色 | 变色:P和U变黑,G变红,递归处理G |
| 情况4 | P为红色,U为黑色,N、P、G呈直线 | 旋转+变色 |
| 情况5 | P为红色,U为黑色,N、P、G呈折线 | 双旋转+变色 |
3.2 变色规则图示
text
情况3(P红,U红):
G(黑) G(红)
/ \ 变色→ / \
P(红) U(红) P(黑) U(黑)
/ /
N(红) N(红)
text
情况4(P红,U黑,LL型):
G(黑) P(黑)
/ \ 右旋→ / \
P(红) U(黑) N(红) G(红)
/ \
N(红) U(黑)
text
情况4(RR型)对称处理(左旋)
情况5(LR型):
G(黑) G(黑) N(黑)
/ \ 左旋→ / \ 右旋→ / \
P(红) U(黑) N(红) U(黑) P(红) G(红)
\ / \
N(红) P(红) U(黑)
情况5(RL型)对称处理(先右旋后左旋)
3.3 插入代码框架
c
// 左旋(与AVL类似,需维护parent)
void leftRotate(RBTree *root, RBNode *x) {
RBNode *y = x->right;
x->right = y->left;
if (y->left != NULL) y->left->parent = x;
y->parent = x->parent;
if (x->parent == NULL) *root = y;
else if (x == x->parent->left) x->parent->left = y;
else x->parent->right = y;
y->left = x;
x->parent = y;
}
// 右旋(对称)
void rightRotate(RBTree *root, RBNode *y) {
// 对称实现...
}
// 插入后调整
void insertFixup(RBTree *root, RBNode *z) {
while (z != *root && z->parent->color == RED) {
if (z->parent == z->parent->parent->left) {
RBNode *y = z->parent->parent->right; // 叔父
if (y != NULL && y->color == RED) {
// 情况3:变色
z->parent->color = BLACK;
y->color = BLACK;
z->parent->parent->color = RED;
z = z->parent->parent;
} else {
if (z == z->parent->right) {
// 情况5:LR型
z = z->parent;
leftRotate(root, z);
}
// 情况4:LL型
z->parent->color = BLACK;
z->parent->parent->color = RED;
rightRotate(root, z->parent->parent);
}
} else {
// 对称情况(P是右孩子)
// ...
}
}
(*root)->color = BLACK;
}
四、C语言模拟(简化版)
由于完整红黑树代码量很大(约300-500行),这里实现一个简化模拟,重点演示插入和调整逻辑。
c
#include <stdio.h>
#include <stdlib.h>
#define RED 0
#define BLACK 1
typedef struct RBNode {
int data;
int color;
struct RBNode *left;
struct RBNode *right;
struct RBNode *parent;
} RBNode, *RBTree;
// 创建节点
RBNode* createNode(int data) {
RBNode *node = (RBNode*)malloc(sizeof(RBNode));
node->data = data;
node->color = RED; // 新节点默认为红色
node->left = NULL;
node->right = NULL;
node->parent = NULL;
return node;
}
// 左旋
void leftRotate(RBTree *root, RBNode *x) {
RBNode *y = x->right;
x->right = y->left;
if (y->left != NULL) y->left->parent = x;
y->parent = x->parent;
if (x->parent == NULL) *root = y;
else if (x == x->parent->left) x->parent->left = y;
else x->parent->right = y;
y->left = x;
x->parent = y;
}
// 右旋
void rightRotate(RBTree *root, RBNode *y) {
RBNode *x = y->left;
y->left = x->right;
if (x->right != NULL) x->right->parent = y;
x->parent = y->parent;
if (y->parent == NULL) *root = x;
else if (y == y->parent->left) y->parent->left = x;
else y->parent->right = x;
x->right = y;
y->parent = x;
}
// 插入调整
void insertFixup(RBTree *root, RBNode *z) {
while (z != *root && z->parent->color == RED) {
if (z->parent == z->parent->parent->left) {
RBNode *y = z->parent->parent->right;
if (y != NULL && y->color == RED) {
// 情况3:叔父红色,变色
z->parent->color = BLACK;
y->color = BLACK;
z->parent->parent->color = RED;
z = z->parent->parent;
} else {
if (z == z->parent->right) {
// 情况5:LR型
z = z->parent;
leftRotate(root, z);
}
// 情况4:LL型
z->parent->color = BLACK;
z->parent->parent->color = RED;
rightRotate(root, z->parent->parent);
}
} else {
// 对称情况(P是右孩子)
RBNode *y = z->parent->parent->left;
if (y != NULL && y->color == RED) {
z->parent->color = BLACK;
y->color = BLACK;
z->parent->parent->color = RED;
z = z->parent->parent;
} else {
if (z == z->parent->left) {
z = z->parent;
rightRotate(root, z);
}
z->parent->color = BLACK;
z->parent->parent->color = RED;
leftRotate(root, z->parent->parent);
}
}
}
(*root)->color = BLACK;
}
// 插入
void insert(RBTree *root, int data) {
RBNode *z = createNode(data);
RBNode *y = NULL;
RBNode *x = *root;
// 普通BST插入
while (x != NULL) {
y = x;
if (z->data < x->data) x = x->left;
else x = x->right;
}
z->parent = y;
if (y == NULL) *root = z;
else if (z->data < y->data) y->left = z;
else y->right = z;
// 调整红黑树性质
insertFixup(root, z);
}
// 中序遍历
void inorder(RBNode *root) {
if (root == NULL) return;
inorder(root->left);
printf("%d(%s) ", root->data, root->color == RED ? "红" : "黑");
inorder(root->right);
}
// 打印树结构(简化)
void printTree(RBNode *root, int level) {
if (root == NULL) return;
printTree(root->right, level + 1);
for (int i = 0; i < level; i++) printf(" ");
printf("%d(%s)\n", root->data, root->color == RED ? "红" : "黑");
printTree(root->left, level + 1);
}
int main() {
RBTree root = NULL;
printf("=== 红黑树插入演示 ===\n");
int values[] = {10, 20, 30, 15, 25, 5, 1};
for (int i = 0; i < 7; i++) {
insert(&root, values[i]);
printf("\n插入 %d 后:\n", values[i]);
printf("中序遍历: ");
inorder(root);
printf("\n树结构:\n");
printTree(root, 0);
}
return 0;
}
运行结果(部分):
text
=== 红黑树插入演示 ===
插入 10 后:
中序遍历: 10(黑)
树结构:
10(黑)
插入 20 后:
中序遍历: 10(黑) 20(红)
树结构:
20(红)
10(黑)
插入 30 后:
中序遍历: 10(红) 20(黑) 30(红)
树结构:
30(红)
20(黑)
10(红)
插入 15 后:
中序遍历: 10(黑) 15(红) 20(黑) 30(黑)
树结构:
30(黑)
20(黑)
15(红)
10(黑)
...
五、红黑树的应用
| 应用 | 说明 |
|---|---|
| C++ STL map/set | 底层是红黑树 |
| Java TreeMap/TreeSet | 底层是红黑树 |
| Linux内核 | 调度、内存管理、文件系统 |
| epoll事件驱动 | 红黑树管理文件描述符 |
| Nginx定时器 | 红黑树管理定时事件 |
六、小结
这一篇我们学习了红黑树的核心知识:
| 要点 | 说明 |
|---|---|
| 五大性质 | 根黑、叶黑、红不连、黑高相同 |
| 插入规则 | 新节点红,根据叔父颜色分情况处理 |
| 核心操作 | 变色、左旋、右旋 |
| 复杂度 | 查找/插入/删除 O(log n) |
| 工程地位 | 最广泛应用的平衡树 |
红黑树 vs AVL树:
-
AVL:严格平衡,查找快,插入删除慢
-
红黑树:大致平衡,查找略慢,插入删除快
下一篇我们讲哈希表。
七、思考题
-
为什么红黑树新插入的节点是红色的?
-
红黑树的性质5(黑高相同)如何保证树的平衡?
-
如果连续插入相同的值,红黑树会如何处理?
-
查找操作比AVL树慢,但为什么工程中更常用红黑树?
欢迎在评论区讨论你的答案。