跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

STL RB-tree 红黑树原理与插入操作实现详解

红黑树是一种自平衡二叉搜索树,通过颜色约束保证路径长度不超过最短路径的两倍。文章解析红黑树的定义、四大规则及节点结构设计。重点讲解插入操作的三种修复场景:叔叔节点红色时变色处理、同侧单旋加变色、异侧双旋加变色。提供旋转操作、查找功能及验证红黑树性质的完整 C++ 代码实现,涵盖中序遍历验证有序性。适合学习 STL 底层容器 map/set 的数据结构原理。

灵魂伴侣发布于 2026/3/15更新于 2026/10/2297 浏览
STL RB-tree 红黑树原理与插入操作实现详解

在这里插入图片描述

一、红黑树介绍

1. 什么是红黑树?

红黑树是一种自平衡的二叉搜索树。它在每个节点上增加一个存储位表示节点的颜色,可以是红色或黑色。通过对任何一条从根到叶子(空节点)路径上节点的颜色进行约束,红黑树确保没有一条路径会比其他路径长出两倍,因此是近似平衡的。

红黑树的平衡不像 AVL 树那样严格,但它的旋转次数更少,插入删除的效率也更高。

2. 红黑树的规则

红黑树的平衡靠以下 4 条规则来维持:

  1. 每个节点不是红色就是黑色
  2. 根节点是黑色的
  3. 如果一个节点是红色的,那么它的两个孩子节点必须是黑色的(即不能有连续的红色节点)
  4. 对于任意节点,从该节点到其所有叶子节点的简单路径上,黑色节点数量相同注意:叶子节点不一定为红色,可以为黑(那是调整过后的结果)。

规则 4 中提到的叶子节点指的是空节点(NIL),它们被认为是黑色的。虽然在实际实现中我们通常不显式处理 NIL 节点,但理解它们有助于理解路径的定义。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

3. 为什么最长路径不超过最短路径的两倍?

  • 根据规则 4,每条路径上黑色节点数相同,假设为 bh(black height)。
  • 最短路径:全黑,长度为 bh。
  • 最长路径:黑 - 红交替,长度为 2 * bh。
  • 因此,任意路径长度 h 满足:bh ≤ h ≤ 2 * bh。

4. 红黑树的效率

假设树中节点数为 N,最短路径长度为 h,则:

2^h - 1 <= N <= 2^(2h) - 1

可得 h ≈ logN,最坏情况下路径长度为 2logN,因此红黑树的查找、插入、删除操作时间复杂度仍为 O(logN)。

相比 AVL 树,红黑树的平衡控制更宽松,旋转次数更少,适合频繁插入删除的场景。

在这里插入图片描述


二、红黑树的实现

2.1 红黑树的节点结构

源码:

typedef bool __rb_tree_color_type; const __rb_tree_color_type __rb_tree_red = false; // 红色为 0 const __rb_tree_color_type __rb_tree_black = true; // 黑色为 1 struct __rb_tree_node_base { typedef __rb_tree_color_type color_type; typedef __rb_tree_node_base* base_ptr; color_type color; // 节点颜色,非红即黑 base_ptr parent; // RB 树的许多操作,必须知道父节点 base_ptr left; // 指向左节点 base_ptr right; // 指向右节点 static base_ptr minimum(base_ptr x) { while (x->left != 0) x = x->left; return x; } static base_ptr maximum(base_ptr x) { while (x->right != 0) x = x->right; // 一直向右走,就会找到最大值, return x; // 这是二叉搜索树的特性 } }; template <class Value> struct _rb_tree_node : public _rb_tree_node_base { typedef _rb_tree_node<Value>* link_type; Value value_field; // 节点值 };

实现:

每个节点除了存储键值对、左右子节点指针、父节点指针外,还需要存储颜色。

// 枚举颜色
enum Colour{ RED, BLACK };// 红黑树节点结构
template<class K,class V>
struct RBTreeNode{
    pair<K, V> _kv;// 键值对
    RBTreeNode<K, V>* _left;// 左孩子
    RBTreeNode<K, V>* _right;// 右孩子
    RBTreeNode<K, V>* _parent;// 父节点(方便向上更新)
    Colour _col;// 节点颜色
    // 构造函数
    RBTreeNode(const pair<K, V>& kv):_kv(kv),_left(nullptr),_right(nullptr),_parent(nullptr),_col(RED)// 默认新节点为红色
    {}
};

新节点为什么默认是红色?
如果插入黑色节点,会直接违反规则 4(黑色节点数变化),修复成本太高。插入红色节点不会改变黑色节点数,只可能违反规则 3(连续红色),更容易修复。

2.2 红黑树整体结构

template<class K,class V>
class RBTree{
    // 类型重命名,方便使用
    typedef RBTreeNode<K, V> Node;
public:
    // 构造函数(默认根节点为空)
    RBTree():_root(nullptr){}
    // 插入接口
    bool Insert(const pair<K, V>& kv);
    // 查找接口
    Node* Find(const K& key);
    // 验证是否满足红黑树性质
    bool IsBalance();
    // 中序遍历(用于验证有序性)
    void InOrder(){_InOrder(_root); cout << endl;}
private:
    // 旋转操作(与 AVL 树类似,但不需要更新平衡因子)
    void RotateL(Node* parent);// 左单旋
    void RotateR(Node* parent);// 右单旋
    // 递归辅助函数
    void _InOrder(Node* root);
    bool Check(Node* root,int blackNum,const int refNum);// 根节点
    Node* _root = nullptr;
};

三、红黑树的插入操作

3.1 插入的大致流程

  1. 按二叉搜索树的规则插入新节点。
  2. 新节点默认是红色。
  3. 如果插入后父节点是黑色,则插入结束(没有违反规则)。
  4. 如果父节点是红色,则违反规则 3(连续红色),需要修复。
  5. 根据叔叔节点的颜色和位置,分情况处理。

3.2 插入后的三种情况

为了方便描述,我们约定:

  • cur:当前新插入的节点(红色)
  • p:父节点(红色,因为如果是黑色就不用处理了)
  • g:祖父节点(一定是黑色,因为不能连续红)
  • u:叔叔节点(p 的兄弟,颜色不确定)
情况 1:叔叔节点存在且为红色(变色处理)

【场景描述】

  • u 存在且为红色
  • p 为红色,g 为黑色

【处理方式】

  • 将 p 和 u 变为黑色
  • 将 g 变为红色
  • 把 g 当作新的 cur,继续向上检查

【原理分析】
把 p 和 u 变黑,解决了 cur 和 p 连续红的问题;g 变红,保证了从 g 到叶子路径上黑色节点数量不变(因为下面少了两个黑,上面加了一个红,黑数不变)。但 g 变红后可能和它的父节点形成连续红,所以需要继续向上检查。

在这里插入图片描述

// 情况 1:叔叔存在且为红
if (uncle && uncle->_col == RED) {
    // 变色
    parent->_col = BLACK;
    uncle->_col = BLACK;
    grandfather->_col = RED;
    // 继续向上处理
    cur = grandfather;
    parent = cur->_parent;
}
情况 2:叔叔节点不存在或为黑色 + cur 和 p 在同一侧(单旋 + 变色)

【场景描述】

  • u 不存在 或 u 存在且为黑色
  • cur 和 p 在同一侧(都是左孩子 或 都是右孩子)

【处理方式】

  • 以 g 为旋转点进行单旋(左左→右旋,右右→左旋)
  • 旋转后:p 变黑,g 变红
  • 调整结束(不需要继续向上)

【原理分析】
为什么单旋后不用继续向上?因为旋转后 p 成了子树的新根,且 p 是黑色,无论它的父节点是什么颜色,都不会违反规则(红节点下面不能接红,但黑节点下面随便接)。

在这里插入图片描述

// 情况 2:左左(右单旋)
if (cur == parent->_left) {
    // 右单旋
    RotateR(grandfather);
    // 变色
    parent->_col = BLACK;
    grandfather->_col = RED;
    break; // 旋转结束,不用继续向上
}
情况 3:叔叔节点不存在或为黑色 + cur 和 p 在不同侧(双旋 + 变色)

【场景描述】

  • u 不存在 或 u 存在且为黑色
  • cur 和 p 在不同侧(p 是左孩子,cur 是右孩子 或 反之)

【处理方式】

  • 先以 p 为旋转点进行单旋(变成情况 2 的形状)
  • 再以 g 为旋转点进行单旋
  • 变色:cur 变黑,g 变红
  • 调整结束

【原理分析】
这种情况是"折线"形状,一次旋转解决不了问题。先旋转成"直线"形状,再按情况 2 处理。

// 情况 3:左右(左右双旋)
else // cur == parent->_right
{
    // 先左旋 p,再右旋 g
    RotateL(parent);
    RotateR(grandfather);
    // 变色
    cur->_col = BLACK;
    grandfather->_col = RED;
    break; // 旋转结束,不用继续向上
}

3.3 插入完整代码

bool Insert(const pair<K, V>& kv){
    // 1. 空树插入
    if(_root == nullptr){
        _root = new Node(kv);
        _root->_col = BLACK;// 根节点必须是黑色
        return true;
    }
    // 2. 查找插入位置
    Node* parent = nullptr;
    Node* cur = _root;
    while(cur){
        if(cur->_kv.first < kv.first){
            parent = cur;
            cur = cur->_right;
        } else if(cur->_kv.first > kv.first){
            parent = cur;
            cur = cur->_left;
        } else{
            return false;// 键值已存在
        }
    }
    // 3. 插入新节点,颜色为红色
    cur = new Node(kv);
    cur->_col = RED;// 新节点默认红色
    if(parent->_kv.first < kv.first)
        parent->_right = cur;
    else
        parent->_left = cur;
    cur->_parent = parent;
    // 4. 修复红黑树性质
    while(parent && parent->_col == RED){
        Node* grandfather = parent->_parent;
        if(parent == grandfather->_left){
            Node* uncle = grandfather->_right;
            // 情况 1:叔叔存在且为红 → 变色
            if(uncle && uncle->_col == RED){
                parent->_col = uncle->_col = BLACK;
                grandfather->_col = RED;
                // 继续向上处理
                cur = grandfather;
                parent = cur->_parent;
            } else{
                // 情况 2:叔叔不存在或为黑
                if(cur == parent->_left){
                    // 左左 → 右单旋
                    RotateR(grandfather);
                    parent->_col = BLACK;
                    grandfather->_col = RED;
                } else{
                    // 左右 → 左右双旋
                    RotateL(parent);
                    RotateR(grandfather);
                    cur->_col = BLACK;
                    grandfather->_col = RED;
                }
                break; // 旋转后子树根变黑,不再影响上层
            }
        } else // parent == grandfather->_right
        {
            Node* uncle = grandfather->_left;
            if(uncle && uncle->_col == RED){
                parent->_col = uncle->_col = BLACK;
                grandfather->_col = RED;
                cur = grandfather;
                parent = cur->_parent;
            } else{
                if(cur == parent->_right){
                    // 右右 → 左单旋
                    RotateL(grandfather);
                    parent->_col = BLACK;
                    grandfather->_col = RED;
                } else{
                    // 右左 → 右左双旋
                    RotateR(parent);
                    RotateL(grandfather);
                    cur->_col = BLACK;
                    grandfather->_col = RED;
                }
                break;
            }
        }
    }
    // 确保根节点始终为黑色
    _root->_col = BLACK;
    return true;
}

3.4 旋转操作的实现

旋转操作和 AVL 树基本一样,只是不需要更新平衡因子。但要注意父指针的更新!

左单旋:

template<class K, class V>
void RBTree<K, V>::RotateL(Node* parent) {
    Node* subR = parent->_right;
    Node* subRL = subR->_left;
    // 1. 将 subRL 链接到 parent 的右
    parent->_right = subRL;
    if (subRL) subRL->_parent = parent;
    // 2. 记录 parent 的原父节点
    Node* pParent = parent->_parent;
    // 3. subR 的左指向 parent
    subR->_left = parent;
    parent->_parent = subR;
    // 4. 处理 subR 与上层节点的链接
    if (parent == _root) {
        // parent 是根节点
        _root = subR;
        subR->_parent = nullptr;
    } else {
        // parent 是局部子树
        if (pParent->_left == parent)
            pParent->_left = subR;
        else
            pParent->_right = subR;
        subR->_parent = pParent;
    }
    // 注意:不需要更新平衡因子!
}

右单旋:

template<class K, class V>
void RBTree<K, V>::RotateR(Node* parent) {
    Node* subL = parent->_left;
    Node* subLR = subL->_right;
    // 1. 将 subLR 链接到 parent 的左
    parent->_left = subLR;
    if (subLR) subLR->_parent = parent;
    // 2. 记录 parent 的原父节点
    Node* pParent = parent->_parent;
    // 3. subL 的右指向 parent
    subL->_right = parent;
    parent->_parent = subL;
    // 4. 处理 subL 与上层节点的链接
    if (parent == _root) {
        _root = subL;
        subL->_parent = nullptr;
    } else {
        if (pParent->_left == parent)
            pParent->_left = subL;
        else
            pParent->_right = subL;
        subL->_parent = pParent;
    }
}

四、红黑树的查找

与普通二叉搜索树相同,时间复杂度 O(logN)。

template<class K,class V>
typename RBTree<K, V>::Node* RBTree<K, V>::Find(const K& key){
    Node* cur = _root;
    while(cur){
        if(cur->_kv.first < key){
            cur = cur->_right;// 去右子树找
        } else if(cur->_kv.first > key){
            cur = cur->_left;// 去左子树找
        } else{
            return cur;// 找到了
        }
    }
    return nullptr;// 没找到
}

五、红黑树的验证

验证一棵树是否是红黑树,不能简单地检查"最长路径≤最短路径的 2 倍",因为即使满足这个条件,也可能不满足颜色规则。必须严格检查 4 条规则。

5.1 验证的思路

  1. 规则 1:枚举类型天然保证,不需要检查
  2. 规则 2:检查根节点是否为黑色
  3. 规则 3:检查是否有连续的红色节点(可以检查红色节点的父节点是否为红色)
  4. 规则 4:检查所有路径的黑色节点数是否相等

在这里插入图片描述

5.2 验证的代码实现

template<class K, class V>
bool RBTree<K, V>::IsBalance() {
    if (_root == nullptr) return true;
    // 规则 2:检查根节点是否为黑色
    if (_root->_col == RED) {
        cout << "错误:根节点为红色" << endl;
        return false;
    }
    // 规则 4:找一个参考值(最左路径的黑色节点数)
    int refNum = 0;
    Node* cur = _root;
    while (cur) {
        if (cur->_col == BLACK) refNum++;
        cur = cur->_left;
    }
    // 递归检查每条路径
    return Check(_root, 0, refNum);
}

template<class K, class V>
bool RBTree<K, V>::Check(Node* root, int blackNum, const int refNum) {
    if (root == nullptr) {
        // 走到空节点,说明一条路径走完了
        // 规则 4:检查黑色节点数是否等于参考值
        if (blackNum != refNum) {
            cout << "错误:黑色节点数量不相等,当前路径有" << blackNum << "个,参考值为" << refNum << endl;
            return false;
        }
        return true;
    }
    // 规则 3:检查是否有连续的红色节点
    // 技巧:检查红色节点的父节点(孩子有两个不好检查,检查父亲更方便)
    if (root->_col == RED && root->_parent && root->_parent->_col == RED) {
        cout << "错误:存在连续的红色节点,节点值为" << root->_kv.first << endl;
        return false;
    }
    // 统计黑色节点
    if (root->_col == BLACK) blackNum++;
    // 递归检查左右子树
    return Check(root->_left, blackNum, refNum) && Check(root->_right, blackNum, refNum);
}

5.3 中序遍历(验证有序性)

template<class K, class V>
void RBTree<K, V>::_InOrder(Node* root) {
    if (root == nullptr) return;
    _InOrder(root->_left);
    cout << root->_kv.first << ":" << root->_kv.second << " ";
    _InOrder(root->_right);
}

在这里插入图片描述

目录

  1. 一、红黑树介绍
  2. 1. 什么是红黑树?
  3. 2. 红黑树的规则
  4. 3. 为什么最长路径不超过最短路径的两倍?
  5. 4. 红黑树的效率
  6. 二、红黑树的实现
  7. 2.1 红黑树的节点结构
  8. 2.2 红黑树整体结构
  9. 三、红黑树的插入操作
  10. 3.1 插入的大致流程
  11. 3.2 插入后的三种情况
  12. 情况 1:叔叔节点存在且为红色(变色处理)
  13. 情况 2:叔叔节点不存在或为黑色 + cur 和 p 在同一侧(单旋 + 变色)
  14. 情况 3:叔叔节点不存在或为黑色 + cur 和 p 在不同侧(双旋 + 变色)
  15. 3.3 插入完整代码
  16. 3.4 旋转操作的实现
  17. 四、红黑树的查找
  18. 五、红黑树的验证
  19. 5.1 验证的思路
  20. 5.2 验证的代码实现
  21. 5.3 中序遍历(验证有序性)

更多推荐文章

查看全部
  • IntelliJ IDEA 切换 Git 用户配置详解
  • Kubernetes 集群管理与核心组件详解
  • C++ 网络编程中的序列化和反序列化实现
  • Java 面向对象入门:类、对象与封装核心详解
  • TWIST2 全身 VR 遥操系统:基于视觉观测预测关节位置的自主策略
  • STM32 运行 AI 大模型的四种主流方案对比与实战案例
  • OpenClaw 多机器人多 Agent 模式:构建 AI 助手团队
  • MoltBot 接入钉钉 Stream 流式模式配置指南
  • 大模型插件与GPTs演进:GPT-4 Turbo特性及RAG、Agent应用解析
  • 基于 Django 框架搭建 WebAPI 项目
  • LeetCode Hot 100 经典题型 Python 刷题记录
  • RabbitMQ 分布式系统实战:从安装部署到 C++ 客户端开发
  • 单链表核心操作全实现与深度解析
  • IPIDEA 网页抓取 API 实战:eBay 商品数据采集与 Python 接入
  • Spring Web 模块核心解析与 RESTful API 实战
  • HarmonyOS 动态轨道生成:实现点击延伸的随机路径系统
  • 15 种典型 RAG 框架及最新技术综述
  • Google Gemini 图像生成模型实测:Nano Banana Pro 免费使用指南
  • TimeGPT 首个时间序列基础模型介绍与实践
  • 二分查找实战:山峰数组峰顶索引与寻找峰值

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online