定位:适配高校期末考核、考研数据结构、互联网秋招底层手撕代码标准

核心特点:内容无删减、逻辑无跳步、严格遵循学术定义,实现原理与底层代码一一对应。精准贴合考情:完整实现秋招必考的旋转、插入、变色底层源码删除模块仅讲解核心思路,不实现底层代码。本文为纯手写红黑树底层教程,与STL map上层调用完全区分,是吃透C++ map底层内核的核心资料。

前置关键区分(必看):此前的Map教程为【STL容器上层API调用】,本文档为【从零手写红黑树底层数据结构】,完整实现节点定义、染色机制、旋转平衡、插入修复核心逻辑,是Map/Set容器的底层核心原理。

一、红黑树前置认知 & 为什么替代AVL树

1.1 平衡树家族对比

AVL树是严格平衡二叉树,要求左右子树高度差严格不超过1,平衡精度极高,但插入、删除操作极易触发失衡,需要频繁旋转调整,数据修改效率较低。

红黑树是弱平衡二叉搜索树,不追求绝对高度平衡,通过一套严格的颜色约束规则,限制树的最长路径与最短路径比例,大幅减少旋转次数,插入删除的整体效率显著优于AVL树

工业级应用结论(面试常考):频繁增删场景优先使用红黑树,频繁查询场景优先使用AVL树。C++ map/set、Java TreeMap、Linux内核进程调度均采用红黑树作为底层数据结构。

1.2 红黑树核心定位

红黑树本质是基于颜色约束实现自平衡的二叉搜索树

所有子树依然满足BST规则:左子树值 < 根 < 右子树值

其平衡机制不依赖节点高度差,而是依靠五大核心性质强制约束树结构,保证最长路径长度不超过最短路径的2倍,稳定维持O(logn)时间复杂度。

二、红黑树五大硬性性质

以下五条为标准学术定义,是期末考试、作业作答、面试口述的标准答案,需精准记忆。

  1. 性质1:每个节点,只能是黑色 或 红色。

  2. 性质2:根节点必须是黑色。

  3. 性质3:所有叶子节点(空节点NIL)均为黑色。

  4. 性质4不存在连续两个红色节点(红节点的孩子一定是黑节点)。

  5. 性质5:从任意节点出发,到达其所有叶子节点的路径上,黑色节点数量相等(黑高一致)。

2.1 五大性质核心推论(

  • 最短路径:全黑色节点

  • 最长路径:红黑交替节点

  • 最长路径长度 ≤ 2 × 最短路径长度

  • 整树高度稳定在 O(logn),不会退化斜链

三、红黑树核心基础概念

3.1 节点颜色定义

标准枚举定义:RED=红色、BLACK=黑色

3.2 NIL 虚拟叶子节点(重点难点)

红黑树摒弃普通BST的空指针判定,将所有空叶子统一替换为黑色NIL虚拟节点

核心作用:统一所有边界判断逻辑,严格保证黑高一致的性质成立

3.3 失衡修复两大手段

红黑树不依赖高度平衡,仅靠两种操作修复所有违规:

  1. 变色:修改节点红/黑颜色,修复红节点连续、黑高不一致问题

  2. 旋转:左旋、右旋,调整树结构,不破坏BST规则

四、基础工具模块(完整手写+逐行注释)

本节包含:颜色枚举、节点结构体、全局NIL节点、构造函数、析构函数、高度获取、基础旋转,全部为底层核心依赖,无任何上层封装。

#include <iostream>
using namespace std;

// 红黑树节点颜色枚举 严格标准定义
enum Color
{
    RED,    // 红色节点
    BLACK   // 黑色节点
};

/**
 * @brief 红黑树节点结构体
 * @param val 节点存储数据
 * @param color 节点颜色
 * @param left/right 左右孩子指针
 * @param parent 父节点指针(红黑树必备,用于回溯祖父、叔叔节点)
 */
struct RBNode
{
    int val;         
    Color color;     
    RBNode* left;    
    RBNode* right;   
    RBNode* parent;  

    // 节点构造函数:新节点默认红色
    RBNode(int v) : val(v), color(RED), left(nullptr), right(nullptr), parent(nullptr) {}
};

// 全局唯一NIL黑色虚拟叶子节点(统一边界、保证黑高规则成立)
RBNode* NIL = new RBNode(0);

/**
 * @brief 红黑树类
 * @note 仅实现【面试必考】插入全套底层逻辑
 * @note 删除仅讲原理,无底层代码
 */
class RBTree
{
private:
    RBNode* root; // 整树根节点

    /**
     * @brief 获取节点颜色,兼容NIL空节点
     * @param node 目标节点
     * @return 节点颜色,空节点统一返回黑色
     */
    Color getColor(RBNode* node)
    {
        return node == NIL ? BLACK : node->color;
    }

    /**
     * @brief 修改节点颜色,过滤NIL空节点
     * @param node 目标节点
     * @param c 目标颜色
     */
    void setColor(RBNode* node, Color c)
    {
        if (node != NIL)
            node->color = c;
    }

    /**
     * @brief 红黑树左旋操作
     * @param x 旋转基准节点
     * @note 仅调整树结构,不修改颜色、不破坏BST性质
     */
    void leftRotate(RBNode* x)
    {
        // y为x的右孩子
        RBNode* y = x->right;
        // y的左子树挂载到x的右子树
        x->right = y->left;

        // 非空子树更新父节点
        if (y->left != NIL)
            y->left->parent = x;

        // y继承x的父节点关系
        y->parent = x->parent;

        // 若x是根节点,y升级为新根
        if (x->parent == nullptr)
            root = y;
        // 若x是父节点左孩子
        else if (x == x->parent->left)
            x->parent->left = y;
        // 若x是父节点右孩子
        else
            x->parent->right = y;

        // x成为y的左孩子
        y->left = x;
        x->parent = y;
    }

    /**
     * @brief 红黑树右旋操作
     * @param y 旋转基准节点
     * @note 仅调整树结构,不修改颜色、不破坏BST性质
     */
    void rightRotate(RBNode* y)
    {
        // x为y的左孩子
        RBNode* x = y->left;
        // x的右子树挂载到y的左子树
        y->left = x->right;

        // 非空子树更新父节点
        if (x->right != NIL)
            x->right->parent = y;

        // x继承y的父节点关系
        x->parent = y->parent;

        // 若y是根节点,x升级为新根
        if (y->parent == nullptr)
            root = x;
        else if (y == y->parent->left)
            y->parent->left = x;
        else
            y->parent->right = x;

        // y成为x的右孩子
        x->right = y;
        y->parent = x;
    }

    /**
     * @brief 插入后红黑树平衡与颜色修复函数
     * @param cur 新插入的节点
     * @note 唯一违规场景:父节点为红色,出现连续红节点
     * @note 修复手段:变色、左旋、右旋、向上回溯
     */
    void insertFix(RBNode* cur)
    {
        // 父节点为红色才存在违规
        while (getColor(cur->parent) == RED)
        {
            // 情况一:父节点是祖父节点的左孩子
            if (cur->parent == cur->parent->parent->left)
            {
                RBNode* p = cur->parent;        // 父节点
                RBNode* g = p->parent;          // 祖父节点
                RBNode* u = g->right;           // 叔叔节点

                // 场景1:叔叔为红色 —— 纯变色修复
                if (getColor(u) == RED)
                {
                    setColor(p, BLACK); // 父变黑
                    setColor(u, BLACK); // 叔叔变黑
                    setColor(g, RED);   // 祖父变红
                    cur = g;            // 向上回溯继续修复
                }
                else
                {
                    // 场景2:叔叔黑、cur是右孩子(LR交叉失衡)
                    // 先左旋转为LL直线失衡
                    if (cur == p->right)
                    {
                        cur = p;
                        leftRotate(cur);
                    }
                    // 场景3:叔叔黑、cur是左孩子(LL直线失衡)
                    setColor(p, BLACK);
                    setColor(g, RED);
                    rightRotate(g);
                }
            }
            // 情况二:父节点是祖父节点的右孩子(镜像对称)
            else
            {
                RBNode* p = cur->parent;
                RBNode* g = p->parent;
                RBNode* u = g->left;

                // 场景1镜像:叔叔红色
                if (getColor(u) == RED)
                {
                    setColor(p, BLACK);
                    setColor(u, BLACK);
                    setColor(g, RED);
                    cur = g;
                }
                else
                {
                    // 场景2镜像:RL交叉失衡,先右旋转RR
                    if (cur == p->left)
                    {
                        cur = p;
                        rightRotate(cur);
                    }
                    // 场景3镜像:RR直线失衡
                    setColor(p, BLACK);
                    setColor(g, RED);
                    leftRotate(g);
                }
            }
        }
        // 强制根节点为黑色,满足性质2
        setColor(root, BLACK);
    }

    /**
     * @brief BST底层插入逻辑
     * @param val 待插入数值
     * @note 新节点默认红色插入,最小化平衡代价
     */
    void insert(int val)
    {
        // 新建节点,默认红色,绑定NIL虚拟叶子
        RBNode* newNode = new RBNode(val);
        newNode->left = NIL;
        newNode->right = NIL;

        // 空树直接初始化根节点
        if (root == nullptr)
        {
            root = newNode;
            root->color = BLACK;
            return;
        }

        // 非空树遍历寻找插入位置
        RBNode* cur = root;
        RBNode* pre = nullptr;
        while (cur != NIL)
        {
            pre = cur;
            if (val < cur->val)
                cur = cur->left;
            else
                cur = cur->right;
        }

        // 挂载新节点,建立父子关系
        newNode->parent = pre;
        if (val < pre->val)
            pre->left = newNode;
        else
            pre->right = newNode;

        // 插入完成,启动平衡修复
        insertFix(newNode);
    }

    /**
     * @brief 中序遍历 BST升序输出
     * @param node 遍历起始节点
     */
    void inOrder(RBNode* node)
    {
        if (node == NIL) return;
        inOrder(node->left);
        cout << node->val << " ";
        inOrder(node->right);
    }

    /**
     * @brief 后序递归销毁整树,防止内存泄漏
     * @param node 销毁起始节点
     */
    void destroy(RBNode* node)
    {
        if (node == NIL) return;
        destroy(node->left);
        destroy(node->right);
        delete node;
    }

public:
    // 构造函数:初始化空树
    RBTree()
    {
        root = nullptr;
    }

    // 析构函数:释放所有节点内存
    ~RBTree()
    {
        destroy(root);
    }

    // 对外公开插入接口
    void insertVal(int val)
    {
        insert(val);
    }

    // 对外公开遍历接口:打印有序序列
    void printTree()
    {
        inOrder(root);
        cout << endl;
    }
};

五、红黑树插入机制全解析

红黑树插入是秋招、期末唯一要求手撕的核心代码,旋转、变色、回溯逻辑必须完整掌握。本节补齐全套完整插入源码,包含BST插入底层逻辑、insertFix平衡修复逻辑,逐行超详细注释,零省略、零跳转。

5.1 插入核心前置原理

新节点默认红色插入。核心原因:

若插入黑色节点,会直接破坏整树黑高统一性质(性质5),需要全局修复,代价极大;

若插入红色节点,仅可能破坏无连续红节点(性质4),仅需局部变色+旋转修复,是最优插入策略。

插入失衡四大核心角色:cur(当前失衡节点)、p(父节点)、g(祖父节点)、u(叔叔节点)

5.2 插入失衡三大标准场景

统一前置条件:插入后父节点p为红色,触发连续红色违规,根据红黑树合法规则,祖父节点g一定为黑色。

场景一:叔叔节点为红色(纯变色修复,无旋转)

结构特征:父红、叔叔红、祖父黑

修复逻辑:父节点、叔叔节点染黑,祖父节点染红;当前节点回溯至祖父节点,向上迭代修复上层失衡。

特点:无需旋转,仅变色即可修复局部失衡。

场景二:叔叔黑色、交叉失衡(LR / RL 先旋转矫正结构)

LR交叉:父为祖父左孩子、cur为父右孩子、叔叔黑 → 左旋父节点,转为LL直线失衡

RL交叉:父为祖父右孩子、cur为父左孩子、叔叔黑 → 右旋父节点,转为RR直线失衡

场景三:叔叔黑色、直线失衡(LL / RR 旋转+变色终结修复)

LL直线:父左、cur左、叔叔黑 → 祖父染红、父染黑、右旋祖父,修复完成无回溯

RR直线:父右、cur右、叔叔黑 → 祖父染红、父染黑、左旋祖父,修复完成无回溯

5.3 完整插入全套底层源码(含BST插入 + 平衡修复)

以下为可直接编译的完整插入核心代码,已整合进红黑树类,注释全覆盖、逻辑无省略。

    /**
     * @brief 底层BST插入逻辑
     * @param val 待插入节点数值
     * @note 1. 新节点默认红色,左右孩子绑定全局NIL黑节点
     * @note 2. 单独处理空树场景,根节点强制黑色
     * @note 3. 遍历寻找合法插入位置,维护父子指针关系
     * @note 4. 插入完成后调用平衡修复函数,恢复红黑树五大性质
     */
    void insert(int val)
    {
        // 实例化新节点:默认红色,初始绑定NIL虚拟叶子节点
        RBNode* newNode = new RBNode(val);
        newNode->left = NIL;
        newNode->right = NIL;

        // 特殊情况:空树,新节点直接作为根节点,根必须为黑色
        if (root == nullptr)
        {
            root = newNode;
            root->color = BLACK;
            return;
        }

        // 非空树:按照BST规则遍历寻找插入位置
        RBNode* cur = root;
        RBNode* pre = nullptr;
        while (cur != NIL)
        {
            pre = cur;                // 记录当前节点为父节点
            if (val < cur->val)      // 小于当前节点,往左子树走
                cur = cur->left;
            else                     // 大于等于当前节点,往右子树走
                cur = cur->right;
        }

        // 挂载新节点,建立父子双向指针关系
        newNode->parent = pre;
        if (val < pre->val)
            pre->left = newNode;
        else
            pre->right = newNode;

        // 插入完成,启动红黑树颜色与结构平衡修复
        insertFix(newNode);
    }

    /**
     * @brief 插入后红黑树自平衡修复函数
     * @param cur 新插入的失衡节点
     * @note 仅解决【连续红节点】违规问题
     * @note 修复手段:变色、左旋、右旋、向上回溯迭代
     */
    void insertFix(RBNode* cur)
    {
        // 循环条件:父节点为红色,出现连续红节点,触发违规
        while (getColor(cur->parent) == RED)
        {
            // 分支1:父节点是祖父节点的左孩子(左侧失衡体系)
            if (cur->parent == cur->parent->parent->left)
            {
                RBNode* p = cur->parent;        // 定义父节点
                RBNode* g = p->parent;          // 定义祖父节点
                RBNode* u = g->right;           // 定义叔叔节点

                // 场景1:叔叔节点为红色 —— 纯变色修复
                if (getColor(u) == RED)
                {
                    setColor(p, BLACK); // 父节点变黑,消除连续红
                    setColor(u, BLACK); // 叔叔节点变黑
                    setColor(g, RED);   // 祖父节点变红,向上传递失衡
                    cur = g;            // 指针回溯至祖父,继续向上校验
                }
                else
                {
                    // 场景2:叔叔黑、当前节点为右孩子(LR交叉失衡)
                    // 先左旋父节点,将交叉结构转为LL直线结构
                    if (cur == p->right)
                    {
                        cur = p;
                        leftRotate(cur);
                    }
                    // 场景3:叔叔黑、当前节点为左孩子(LL直线失衡)
                    // 变色+右旋,一次性终结修复
                    setColor(p, BLACK);
                    setColor(g, RED);
                    rightRotate(g);
                }
            }
            // 分支2:父节点是祖父节点的右孩子(右侧镜像失衡体系)
            else
            {
                RBNode* p = cur->parent;
                RBNode* g = p->parent;
                RBNode* u = g->left;

                // 场景1镜像:叔叔红色,纯变色修复
                if (getColor(u) == RED)
                {
                    setColor(p, BLACK);
                    setColor(u, BLACK);
                    setColor(g, RED);
                    cur = g;
                }
                else
                {
                    // 场景2镜像:RL交叉失衡,先右旋转为RR直线
                    if (cur == p->left)
                    {
                        cur = p;
                        rightRotate(cur);
                    }
                    // 场景3镜像:RR直线失衡,左旋+变色终结修复
                    setColor(p, BLACK);
                    setColor(g, RED);
                    leftRotate(g);
                }
            }
        }
        // 强制根节点为黑色,严格遵守红黑树性质2
        setColor(root, BLACK);
    }

5.4 插入代码核心逻辑总结

1、先按照标准BST规则完成节点插入,新节点默认红色;

2、判断是否出现连续红节点违规,若父节点为黑色则无需修复;

3、根据叔叔节点颜色、当前节点位置,区分三大场景;

4、纯变色场景向上回溯,旋转场景直接终结修复;

5、最后强制根节点变黑,保证五大性质全部成立。

秋招/面试核心结论(重点记忆):红黑树删除底层源码极其复杂,包含双重黑色消解、四套镜像场景、多层回溯修复,常规秋招面试、期末考试均不要求手写删除底层代码,仅需掌握核心思路即可。本文档不提供任何删除底层代码,完全贴合面试考情。

6.1 删除模块核心认知

红黑树插入仅破坏「无连续红节点」规则,修复简单;而删除操作会直接破坏黑高一致性(五大核心性质5),产生「双重黑色节点」,是红黑树最复杂的逻辑。

删除整体执行流程(仅理解思路,无需写代码):

  1. 按照标准BST规则找到待删除节点,区分叶子节点、单孩子节点、双孩子节点三种情况;

  2. 双孩子节点采用「后继节点替换法」,用右子树最小节点覆盖待删除节点值,再删除后继节点;

  3. 判断被删除节点颜色:删除红色节点无任何影响,无需修复;删除黑色节点会产生双重黑色,破坏整树黑高平衡;

  4. 通过兄弟节点、兄弟节点左右孩子的颜色组合,匹配4种修复场景,逐层向上消解双重黑色,最终恢复红黑树五大性质。

6.2 面试必背问答

场景一:叔叔节点为红色(纯变色修复,无需旋转)

结构特征:父节点红、叔叔节点红、祖父节点黑

修复逻辑:父、叔叔节点染黑,祖父节点染红;随后将当前节点回溯至祖父节点,向上继续校验平衡,直到根节点。

核心特点:无需旋转,仅变色即可修复局部失衡,是最简单的插入失衡场景。

场景二:叔叔节点黑色、交叉失衡(LR / RL,先旋转再修复)

以左侧LR为例:父节点是祖父左孩子、当前节点是父节点右孩子、叔叔节点黑色

修复逻辑:先对父节点左旋,将交叉结构转换为直线结构(LL场景),再进入场景三修复。

右侧RL镜像:先对父节点右旋,转换为RR直线结构。

场景三:叔叔节点黑色、直线失衡(LL / RR,旋转+变色修复)

以左侧LL为例:父节点是祖父左孩子、当前节点是父节点左孩子、叔叔节点黑色

修复逻辑:祖父节点染红,父节点染黑,对祖父节点右旋,一次性修复所有失衡,无需继续回溯。

右侧RR镜像:祖父染红、父节点染黑,对祖父节点左旋修复。

七、完整测试主函数

int main()
{
    // 实例化红黑树对象
    RBTree tree;

    // 乱序测试数据(验证自动排序、自动变色、自动旋转平衡)
    int arr[] = {10, 20, 30, 15, 25, 5};
    int n = sizeof(arr) / sizeof(arr[0]);

    // 批量插入节点
    for (int i = 0; i < n; i++)
    {
        tree.insertVal(arr[i]);
    }

    // 中序遍历一定升序,验证BST特性与平衡正确性
    cout << "红黑树插入完成 · 中序升序遍历结果:" << endl;
    tree.printTree();

    return 0;
}

六、工具函数、测试代码与重难点总结

8.1 插入核心考点必背

  • 根黑、叶黑、节点红黑二色、无连续红、任意路径黑高相等

  • 新节点默认红色插入,代价最小

8.2 插入修复三大场景答题模板

  1. 叔叔红:仅变色,向上回溯

  2. 叔叔黑、LR:先左旋转LL,再右旋+变色

  3. 叔叔黑、LL:直接右旋+变色

8.3 删除考点总结

  • 删除核心难点:删除黑色节点产生双重黑色,破坏全局黑高一致性

  • 修复逻辑:依靠兄弟节点、兄弟子节点颜色组合,4种场景逐层消解双重黑色

  • 应试结论:秋招面试、基础期末考核,无需掌握删除底层代码,仅需理解原理

8.4 红黑树 vs AVL树

  • AVL树:严格平衡,查询效率高,增删旋转多、效率低

  • 红黑树:弱平衡,允许局部不平衡,旋转少、增删效率高,工业级首选

更多推荐