红黑树完全指南:从五条性质到完整插入删除实现

引言

在前面的树系列中,我们学习了二叉搜索树(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),是工程界最广泛使用的平衡树。

相关推荐
JieE21212 小时前
反转链表:从双指针到递归,吃透链表反转的核心逻辑
javascript·算法
玖釉-13 小时前
旋转图像:从矩阵转置、镜像到坐标变换的系统理解
c++·windows·算法·图形渲染
fengenrong13 小时前
20260522
算法
一条大祥脚13 小时前
Codeforces Round 1099 (Div. 2) 构造|贪心|图论|还原数组
java·算法·图论
Sheldon Chao14 小时前
Lecture 7 基于策略梯度的算法
人工智能·算法·机器学习
始三角龙14 小时前
LeetCode hoot 100 -- 缺失的第一个正整数
算法·leetcode·职场和发展
飞Link14 小时前
深度解析孪生网络(Siamese Network):从原理、技巧到实战应用
算法·数据挖掘·回归
测试狗科研平台14 小时前
洞悉微观电荷流动,VASP计算电荷密度分布
算法·云计算·开源软件
Sarvartha14 小时前
单链表的顺序建立与结点的删除(期末题复现)
数据结构