跳到主要内容
极客日志极客日志
首页博客AI提示词GitHub精选代理工具
搜索
|注册
博客列表
C++算法

C++ 进阶:STL 红黑树实现与原理分析

综述由AI生成红黑树是一种自平衡二叉搜索树,通过颜色标记和旋转操作维持近似平衡。其核心特性包括根节点黑色、红节点子节点必黑、任意路径黑节点数相同等。详细讲解了红黑树的查找、插入(含变色、单旋、双旋)及验证逻辑,并对比了其与 AVL 树的性能差异。代码部分提供了完整的 C++ 模板实现,涵盖节点结构、旋转操作及测试用例,适合深入理解 STL 容器底层机制。

BigDataPan发布于 2026/3/24更新于 2026/5/14 浏览
C++ 进阶:STL 红黑树实现与原理分析

概念介绍

什么是红黑树?

红黑树是一种自平衡二叉搜索树,由德国计算机科学家 Rudolf Bayer 在 1972 年发明。它通过额外的颜色标记和旋转操作来维持树的平衡,确保最坏情况下的基本操作(插入、删除、查找)时间复杂度为 O(log n)。它在 C++ STL 的 map 和 set 底层实现中广泛使用。

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

红黑树的基本特性

  1. 节点颜色属性:每个节点的颜色只能是红色或者黑色。
  2. 根节点与叶子节点:根节点是黑色的;叶子节点(NIL 节点,即空指针)也是黑色的。
  3. 红节点规则:如果一个节点是红色的,那么它的两个子节点都是黑色的(不存在连续的红色节点)。
  4. 黑色高度:对每个节点,从该节点到其所有后代叶子节点的简单路径上,均包含相同数目的黑色节点。

红黑树示例

红黑树的效率怎么样?

相比普通的二叉搜索树,红黑树避免了极端情况下退化为链表的问题。假设红黑树中结点数量为 N,最短路径长度为 h,则满足 $2^h - 1 \leq N < 2^{2h} - 1$。由此可推出 $h \approx \log N$,这意味着红黑树增删查改操作的最坏情况,是走最长路径 $2 \times \log N$,所以时间复杂度仍为 O(log N)。

与 AVL 树相比,红黑树的平衡调整操作相对稳定,虽然插入和删除时会进行旋转和变色,但代价相对较小,不会引起树结构的剧烈变化。插入相同数量结点时,红黑树的旋转次数通常更少,因为它对平衡的控制没那么严格。

红黑树如何确保最长路径不超过最短路径的 2 倍?

  • 最短路径(全黑路径):由规则 4 可知,极端场景下,最短路径就是全由黑色结点组成的路径。记为 bh(black height)。
  • 最长路径(黑红间隔路径):结合规则 2 和 3,极端场景下,最长路径会呈现一黑一红交替的结构,长度为 2*bh。
  • 路径长度范围:任意从根到 NIL 结点的路径长度 h,都满足 bh ≤ h ≤ 2*bh,这保证了红黑树的近似平衡特性。

基本操作

一、查找操作

红黑树的查找操作核心逻辑与二叉搜索树高度一致。依托'左小右大'的基本规则,从根节点出发,通过键值的比较,不断向左右子树递归或迭代查找。差异主要体现在平衡维护机制上,但查找操作的'二分比较'形式始终保持简洁高效。

二、插入操作

1. 本质

红黑树的插入是在二叉搜索树的基础上,通过颜色调整和旋转操作来维持树的近似平衡特性。

2. 步骤

在红黑树插入调整逻辑里,我们统一做如下标识:新插入的节点记为 c(current),父节点记为 p(parent),祖父节点记为 g(grandfather),叔叔节点记为 u(uncle)。

情况 1:变色

当新插入节点 c 为红色、父节点 p 为红色,且叔叔节点 u 存在且为红色时:

  • 颜色调整:将 p 和 u 染为黑色,g 染为红色。
  • :把 视为新的'当前节点',继续向上检查调整。
向上回溯
g
  • 原理:黑色高度守恒,解决连续红节点问题。若 g 变为根节点,需再染回黑色。
  • 情况 2:变色 + 单旋

    当叔叔节点 u 不存在,或 u 存在但为黑色时,仅靠变色无法解决连续红节点问题,需结合旋转 + 变色。

    • 场景 A(左左型):p 是 g 的左孩子,c 是 p 的左孩子。以 g 为旋转中心右单旋,p 染黑,g 染红。
    • 场景 B(右右型):p 是 g 的右孩子,c 是 p 的右孩子。以 g 为旋转中心左单旋,p 染黑,g 染红。
    情况 3:变色 + 双旋

    当叔叔节点 u 不存在,或 u 存在但为黑色,且结构为左右型或右左型时:

    • 场景 1(左右型):p 是 g 的左孩子,c 是 p 的右孩子。先以 p 为中心左单旋,再以 g 为中心右单旋。变色:c 染黑,g 染红。
    • 场景 2(右左型):p 是 g 的右孩子,c 是 p 的左孩子。先以 p 为中心右单旋,再以 g 为中心左单旋。变色:c 染黑,g 染红。

    三、验证操作

    直接通过最长路径与最短路径的倍数关系来验证红黑树是不可行的。正确的做法是直接校验红黑树的 4 条核心规则:

    1. 颜色合法性:枚举定义天然保证。
    2. 根节点颜色:直接检查根节点是否为黑色。
    3. 红色节点的子节点合法性:反向校验,遍历节点时检查当前节点的父节点颜色。若父节点为红色且当前节点也为红色,即违规。
    4. 路径黑色节点数量一致性:通过前序遍历 + 传递黑色节点计数实现。以第一条路径的黑色节点数量为参考值,后续逐一对比。
    // 判断一棵树是不是红黑树
    bool IsRBTree() {
        // 特殊情况:空树
        if (_root == nullptr) return true;
        // 特殊情况:根节点不能为红色
        if (_root->_col == RED) return false;
    
        // 第一步:计算最左路径上节点的数量
        int refNum = 0;
        Node* current = _root;
        while (current) {
            if (current->_col == BLACK) ++refNum;
            current = current->_left;
        }
    
        // 第二步:递归检查其他路径上面的黑色节点的数量
        return Check(_root, 0, refNum);
    }
    
    bool Check(Node* root, int blackNum, const int refNum) {
        if (root == nullptr) {
            if (refNum != blackNum) {
                cout << "存在黑色结点的数量不相等的路径" << endl;
                return false;
            }
            return true;
        }
    
        // 检查连续红节点的情况
        if (root->_col == RED && 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);
    }
    

    代码实现

    红黑树的存储结构

    节点的存储结构

    红黑树节点需要存储键值对、左右子节点指针、父节点指针以及颜色信息。

    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) {}
    };
    
    树的存储结构

    类模板封装了根节点指针及各类操作接口。

    template<class K, class V>
    class RBTree {
    private:
        typedef RBTreeNode<K, V> Node;
        Node* _root = nullptr;
    
        void _InOrder(Node* root) {
            if (root == nullptr) return;
            _InOrder(root->_left);
            cout << root->_kv.first << ":" << root->_kv.second << endl;
            _InOrder(root->_right);
        }
    
        bool Check(Node* root, int blackNum, const int refNum) {
            if (root == nullptr) {
                if (refNum != blackNum) return false;
                return true;
            }
            if (root->_col == RED && root->_parent->_col == RED) return false;
            if (root->_col == BLACK) blackNum++;
            return Check(root->_left, blackNum, refNum) && Check(root->_right, blackNum, refNum);
        }
    
    public:
        Node* Find(const K& key) {
            Node* curr = _root;
            while (curr) {
                if (curr->_kv.first < key) curr = curr->_right;
                else if (curr->_kv.first > key) curr = curr->_left;
                else return curr;
            }
            return nullptr;
        }
    
        bool Insert(const pair<K, V>& kv) {
            if (_root == nullptr) {
                _root = new Node(kv);
                _root->_col = BLACK;
                return true;
            }
    
            Node* current = _root;
            Node* parent = nullptr;
    
            while (current) {
                if (current->_kv.first < kv.first) {
                    parent = current;
                    current = current->_right;
                } else if (current->_kv.first > kv.first) {
                    parent = current;
                    current = current->_left;
                } else return false;
            }
    
            current = new Node(kv);
            current->_col = RED;
            current->_parent = parent;
    
            if (kv.first < parent->_kv.first) parent->_left = current;
            else parent->_right = current;
    
            while (parent && parent->_col == RED) {
                Node* grandfather = parent->_parent;
                if (parent == grandfather->_left) {
                    Node* uncle = grandfather->_right;
                    if (uncle && uncle->_col == RED) {
                        parent->_col = BLACK;
                        uncle->_col = BLACK;
                        grandfather->_col = RED;
                        current = grandfather;
                        parent = grandfather->_parent;
                    } else {
                        if (current == parent->_left) {
                            RotateR(grandfather);
                            parent->_col = BLACK;
                            grandfather->_col = RED;
                        } else {
                            RotateL(parent);
                            RotateR(grandfather);
                            current->_col = BLACK;
                            grandfather->_col = RED;
                        }
                        break;
                    }
                } else {
                    Node* uncle = grandfather->_left;
                    if (uncle && uncle->_col == RED) {
                        parent->_col = BLACK;
                        uncle->_col = BLACK;
                        grandfather->_col = RED;
                        current = grandfather;
                        parent = grandfather->_parent;
                    } else {
                        if (current == parent->_right) {
                            RotateL(grandfather);
                            parent->_col = BLACK;
                            grandfather->_col = RED;
                        } else {
                            RotateR(parent);
                            RotateL(grandfather);
                            current->_col = BLACK;
                            grandfather->_col = RED;
                        }
                        break;
                    }
                }
            }
            _root->_col = BLACK;
            return true;
        }
    
        void RotateR(Node* parent) {
            Node* subL = parent->_left;
            Node* subLR = parent->_left->_right;
            Node* pParent = parent->_parent;
    
            parent->_left = subLR;
            if (subLR) subLR->_parent = parent;
    
            parent->_parent = subL;
            subL->_right = parent;
    
            if (parent == _root) {
                _root = subL;
                subL->_parent = nullptr;
            } else {
                if (parent == pParent->_left) pParent->_left = subL;
                else pParent->_right = subL;
                subL->_parent = pParent;
            }
        }
    
        void RotateL(Node* parent) {
            Node* subR = parent->_right;
            Node* subRL = parent->_right->_left;
            Node* pParent = parent->_parent;
    
            parent->_right = subRL;
            if (subRL) subRL->_parent = parent;
    
            parent->_parent = subR;
            subR->_left = parent;
    
            if (parent == _root) {
                _root = subR;
                subR->_parent = nullptr;
            } else {
                if (parent == pParent->_left) pParent->_left = subR;
                else pParent->_right = subR;
                subR->_parent = pParent;
            }
        }
    
        void InOrder() {
            _InOrder(_root);
            cout << endl;
        }
    
        bool IsRBTree() {
            if (_root == nullptr) return true;
            if (_root->_col == RED) return false;
            int refNum = 0;
            Node* current = _root;
            while (current) {
                if (current->_col == BLACK) ++refNum;
                current = current->_left;
            }
            return Check(_root, 0, refNum);
        }
    };
    
    测试文件
    #define _CRT_SECURE_NO_WARNINGS 1
    #include "RBTree.h"
    void TestRBTree() {
        RBTree<int, int> rbTree;
        int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
        for (auto e : a) rbTree.Insert({ e, e });
    
        std::cout << "中序遍历结果:" << std::endl;
        rbTree.InOrder();
    
        std::cout << "红黑树平衡性验证结果:" << (rbTree.IsRBTree() ? "平衡(1)" : "不平衡(0)") << std::endl;
    
        int keyToFind = 7;
        auto foundNode = rbTree.Find(keyToFind);
        if (foundNode) std::cout << "找到节点:" << foundNode->_kv.first << ":" << foundNode->_kv.second << std::endl;
        else std::cout << "未找到节点:" << keyToFind << std::endl;
    
        std::cout << "树的高度:" << rbTree.Height() << std::endl;
        std::cout << "节点数量:" << rbTree.Size() << std::endl;
    }
    int main() {
        TestRBTree();
        return 0;
    }
    

    运行结果

    终极对决:AVL 树 vs 红黑树

    性能对比

    为了直观感受两者的差异,我们编写了一个测试程序,在一百万数据规模下对比插入和查找的性能。

    #include "AVLTree.h"
    #include "RBTree.h"
    #include <vector>
    #include <ctime>
    using namespace std;
    
    void TestBST() {
        cout << "测试一百万的数据规模下 AVL 树和红黑树的性能差距\n" << endl;
        const int N = 1000000;
        vector<int> v;
        v.reserve(N);
        srand(time(0));
        for (size_t i = 0; i < N; i++) v.push_back(rand() + i);
    
        size_t begin1 = clock();
        AVLTree<int, int> avl;
        for (auto it : v) avl.Insert(make_pair(it, it));
        size_t end1 = clock();
    
        size_t begin2 = clock();
        RBTree<int, int> rb;
        for (auto it : v) rb.Insert(make_pair(it, it));
        size_t end2 = clock();
    
        cout << "-----------插入操作的耗时-----------" << endl;
        cout << "AVL Insert:" << end1 - begin1 << endl;
        cout << "RB Insert:" << end2 - begin2 << endl;
    
        size_t begin3 = clock();
        for (auto it : v) avl.Find(it);
        size_t end3 = clock();
    
        size_t begin4 = clock();
        for (auto it : v) rb.Find(it);
        size_t end4 = clock();
    
        cout << "\n-----------查找操作的耗时-----------" << endl;
        cout << "AVL Find:" << end3 - begin3 << endl;
        cout << "RB Find:" << end4 - begin4 << endl;
    
        cout << "\n-----------是否平衡-----------" << endl;
        cout << "AVL IsBalance:" << avl.IsAVLTree() << endl;
        cout << "RB IsBalance:" << rb.IsRBTree() << endl;
    
        cout << "\n-----------树的高度-----------" << endl;
        cout << "AVL Height:" << avl.Height() << endl;
        cout << "RB Height:" << rb.Height() << endl;
    
        cout << "\n-----------插入节点的数量-----------" << endl;
        cout << "AVL Size:" << avl.Size() << endl;
        cout << "RB Size:" << rb.Size() << endl;
    }
    
    int main() {
        TestBST();
        return 0;
    }
    

    结论

    从测试结果可以看出,红黑树在插入操作上通常比 AVL 树更快,因为红黑树不需要像 AVL 树那样频繁地重新平衡。而在查找操作上,两者性能相当。在实际工程中,如 C++ STL 选择红黑树作为 map 和 set 的底层容器,正是看中了其在动态插入场景下的整体稳定性。

    目录

    1. 概念介绍
    2. 什么是红黑树?
    3. 红黑树的基本特性
    4. 红黑树的效率怎么样?
    5. 红黑树如何确保最长路径不超过最短路径的 2 倍?
    6. 基本操作
    7. 一、查找操作
    8. 二、插入操作
    9. 1. 本质
    10. 2. 步骤
    11. 情况 1:变色
    12. 情况 2:变色 + 单旋
    13. 情况 3:变色 + 双旋
    14. 三、验证操作
    15. 代码实现
    16. 红黑树的存储结构
    17. 节点的存储结构
    18. 树的存储结构
    19. 测试文件
    20. 终极对决:AVL 树 vs 红黑树
    21. 性能对比
    22. 结论
    • 💰 8折买阿里云服务器限时8折了解详情
    • GPT-5.5 超高智商模型1元抵1刀ChatGPT中转购买
    • 代充Chatgpt Plus/pro 帐号了解详情
    • 🤖 一键搭建Deepseek满血版了解详情
    • 一键打造专属AI 智能体了解详情
    极客日志微信公众号二维码

    微信扫一扫,关注极客日志

    微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

    更多推荐文章

    查看全部
    • 微服务链路追踪实战:SkyWalking 与 Zipkin 对比及优化
    • OpenClaw AI 编程上下文 Token 限制剖析与扩容方案
    • ToDesk、顺网云与海马云:DeepSeek 大模型云端部署实测对比
    • C++ STL 容器常用函数与实战技巧
    • OpenClaw 对接 Stable Diffusion 教程:免费畅享 AI 绘画
    • Ubuntu 系统下 Python 连接 KingbaseES 数据库实现增删改查
    • Rust、Go、Java、Python、Node.js 内存管理深度对比
    • AI 提示词模板:3 分钟生成 3000 字电商产品详情页文案
    • ThreadLocal 原理、使用场景及内存泄漏问题解析
    • 火影忍者主题静态网页设计与实现指南
    • Seedream 4.0 深度测评:AI 图像生成与企业级应用
    • 配置钉钉 OpenClaw 机器人调用 OpenMetadata
    • BFS 解决 FloodFill 算法:从图像渲染到岛屿问题实战
    • 如何成为懂 AI 的产品经理
    • Python 实现月相计算与可视化效果展示
    • Spring Boot 3 优雅停机配置与实战
    • Java 导出 Excel 的主流方案:POI、EasyExcel 与模板引擎实战
    • WebUI 图像抠图工具使用教程:U-Net 架构与参数调优
    • 二分算法实战:A-B 数对与高考志愿问题
    • LeetCode 48:旋转图像的原地算法解析与实现

    相关免费在线工具

    • 加密/解密文本

      使用加密算法(如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