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

C++ 模拟实现红黑树 (RBTree)

对比 AVL 树介绍了红黑树的概念与实现。红黑树通过节点颜色标记和五条核心规则实现近似平衡,保证任意路径长度差不超过 2 倍,时间复杂度为 O(logN)。重点讲解了插入操作中的三种修复场景:叔叔节点红色时变色处理,叔叔节点黑色时单旋或双旋加变色处理。提供了完整的 C++ 结构定义、旋转函数、插入、查找及辅助函数的代码实现,并修正了部分语法错误以增强可用性。

机器人发布于 2026/3/21更新于 2026/9/1162 浏览

一、红黑树的概念

红黑树是自平衡二叉搜索树,通过节点颜色(红 / 黑)和严格的规则约束,保证任意根到叶子的路径长度差不超过 2 倍,实现近似平衡,避免普通二叉搜索树退化为链表。

1.1 红黑树的规则(必须牢记)

  • 每个节点非红即黑;
  • 根节点必须是黑色;
  • 红色节点的子节点必须全为黑色(禁止连续红节点);
  • 任意节点到其所有 NULL 叶子节点的路径,黑色节点数量相同(简称'黑高一致')。

1.2 为什么最长路径不超过最短路径 2 倍?

  • 最短路径:全黑节点路径,黑高为 bh;
  • 最长路径:黑红交替路径,长度为 2*bh;
  • 结论:任意路径长度 bh ≤ h ≤ 2*bh,保证了红黑树的近似平衡。

1.3 红黑树的效率优势

  • 时间复杂度:增删查改均为 O(logN)(与 AVL 树同级);
  • 性能优势:插入 / 删除时旋转次数更少(AVL 树要求严格平衡,红黑树仅'近似平衡'),实际工程中(如 STL map/set)更常用。

二、红黑树的实现

2.1 红黑树的结构定义

#include <iostream>
#include <utility>
using namespace std;

// 枚举值表示颜色
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) {
        // 默认红(插入时默认红,减少黑高破坏)
    }
};

// 红黑树类
template<class K, class V>
class RBTree {
    using Node = RBTreeNode<K, V>;
public:
    // 插入接口
    bool Insert(const pair<K, V>& kv);
    // 中序遍历(验证二叉搜索树特性)
    void InOrder() { _InOrder(_root); cout << endl; }
    // 查找接口
    Node* Find(const K& key);
    // 获取树的大小
    int Size() { return _Size(_root); }
    // 获取树的高度
    int Height() { return _Height(_root); }

private:
    // 私有递归函数
    void _InOrder(Node* root);
    int _Size(Node* root);
    int _Height(Node* root);
    // 旋转函数(核心操作)
    void RotateL(Node* parent); // 左旋转
    void RotateR(Node* parent); // 右旋转
    // 验证辅助函数
    bool _IsValidRBTree(Node* root, int blackCount, int& refBlackCount);

private:
    Node* _root = nullptr;
};

2.2 核心操作:旋转函数

旋转是红黑树维护平衡的核心,分为左旋转和右旋转,需保证旋转后仍符合二叉搜索树规则。

// 左旋转:以 parent 为旋转点,右孩子上位
void RotateL(Node* parent) {
    Node* subR = parent->_right; // 失衡节点的右孩子(新根)
    Node* subRL = subR->_left; // 新根的左子树(需要转移)
    Node* pParent = parent->_parent; // 失衡节点的父节点

    // 1. 转移 subRL:挂到 parent 的右子树
    parent->_right = subRL;
    if (subRL) subRL->_parent = parent;

    // 2. 父节点降级:parent 作为 subR 的左孩子
    subR->_left = parent;
    parent->_parent = subR;

    // 3. 链接新根到原父节点
    if (pParent == nullptr) {
        _root = subR;
        subR->_parent = nullptr;
    } else {
        if (pParent->_left == parent) pParent->_left = subR;
        else pParent->_right = subR;
        subR->_parent = pParent;
    }
}

// 右旋转:以 parent 为旋转点,左孩子上位
void RotateR(Node* parent) {
    Node* subL = parent->_left; // 失衡节点的左孩子(新根)
    Node* subLR = subL->_right; // 新根的右子树(需要转移)
    Node* pParent = parent->_parent; // 失衡节点的父节点

    // 1. 转移 subLR:挂到 parent 的左子树
    parent->_left = subLR;
    if (subLR) subLR->_parent = parent;

    // 2. 父节点降级:parent 作为 subL 的右孩子
    subL->_right = parent;
    parent->_parent = subL;

    // 3. 链接新根到原父节点
    if (pParent == nullptr) {
        _root = subL;
        subL->_parent = nullptr;
    } else {
        if (pParent->_left == parent) pParent->_left = subL;
        else pParent->_right = subL;
        subL->_parent = pParent;
    }
}

2.3 核心操作:插入函数

  1. 插入一个值按二叉搜索树规则进行插入,插入后我们只需要观察是否符合红黑树的 4 条规则。
  2. 如果是空树插入,新增结点是黑色结点。如果是非空树插入,新增结点必须红色结点,因为非空树插入,新增黑色结点就破坏了规则 4,规则 4 是很难维护的。
  3. 非空树插入后,新增结点必须红色结点,如果父亲结点是黑色的,则没有违反任何规则,插入结束。
  4. 非空树插入后,新增结点必须红色结点,如果父亲结点是红色的,则违反规则 3。进一步分析,c 是红色,p 为红,g 必为黑,这三个颜色都固定了,关键的变化看 u 的情况,需要根据 u 分为以下几种情况分别处理。

说明:下图中假设我们把新增结点标识为 c(cur),c 的父亲标识为 p(parent),p 的父亲标识为 g(grandfather), p 的兄弟标识为 u(uncle)。

2.2.2 情况 1:变色

插入的 c 为红,p 为红,g 为黑,u 存在且为红,则将 p 和 u 变黑,g 变红。然后把 g 当作新的 c,继续往上更新。

分析:因为 p 和 u 都是红色,g 是黑色,把 p 和 u 变黑,左边子树路径各增加一个黑色结点,g 再变红,相当于保持 g 所在子树的黑色结点的数量不变,同时解决了 c 和 p 连续红色结点的问题,需要继续往上更新是因为,g 是红色,如果 g 的父亲还是红色,那么就还需要继续处理;如果 g 的父亲是黑色,则处理结束了;如果 g 就是整棵树的根,再把 g 变回黑色。

情况 1 只变色,不旋转。所以无论 c 是 p 的左还是右,p 是 g 的左还是右,都是上面的变色处理方式。

2.2.3 情况 2:单旋 + 变色

c 为红,p 为红,g 为黑,u 不存在或者 u 存在且为黑。

  • u 不存在,则 c 一定是新增节点。
  • u 存在且为黑,c 一定不是新增节点,c 之前是黑色的,是在 c 的子树中插入,符合情况 1,变色将 c 从黑色变成红色,更新上来的。

p 必须变黑,连续红色节点的问题,u 不存在或者是黑色的,这里单纯的变色无法解决问题,需要旋转 + 变色

如果 p 是 g 的左,c 是 p 的左,那么以 g 为旋转点进行右单旋,再把 p 变黑,g 变红即可。p 变成这颗树新的根,这样子树黑色结点的数量不变,没有连续的红色结点了,且不需要往上更新,因为 p 的父亲是黑色还是红色或者空都不违反规则。

如果 p 是 g 的右,c 是 p 的右,那么以 g 为旋转点进行左单旋,再把 p 变黑,g 变红即可。p 变成这颗树新的根,这样子树黑色结点的数量不变,没有连续的红色结点了,且不需要往上更新,因为 p 的父亲是黑色还是红色或者空都不违反规则。

2.2.4 情况 3:双旋 + 变色

c 为红,p 为红,g 为黑,u 不存在或者 u 存在且为黑,u 不存在,则 c 一定是新增结点,u 存在且为黑,则 c 一定不是新增,c 之前是黑色的,是在 c 的子树中插入,符合情况 1,变色将 c 从黑色变成红色,更新上来的。

p 必须变黑,才能解决,连续红色结点的问题,u 不存在或者是黑色的,这里单纯的变色无法解决问题,需要旋转 + 变色。

如果 p 是 g 的左,c 是 p 的右,那么先以 p 为旋转点进行左单旋,再以 g 为旋转点进行右单旋,再把 c 变黑,g 变红即可。c 变成这颗树新的根,这样子树黑色结点的数量不变,没有连续的红色结点了,且不需要往上更新,因为 c 的父亲是黑色还是红色或者空都不违反规则。

如果 p 是 g 的右,c 是 p 的左,那么先以 p 为旋转点进行右单旋,再以 g 为旋转点进行左单旋,再把 c 变黑,g 变红即可。c 变成这颗树新的根,这样子树黑色结点的数量不变,没有连续的红色结点了,且不需要往上更新,因为 c 的父亲是黑色还是红色或者空都不违反规则。

2.2.5 插入核心问题总结

关键看 u(叔叔),p(父亲) 和 g(爷爷) 是固定的,方向不一定固定分两种情况,if(情况 1)、else(情况 2),但颜色一定是固定的。

  • u(叔叔) 存在且为红,就可以和 p(父亲) 一起分担颜色,把 u(叔叔) 和 p(父亲) 变黑,g(爷爷) 变红
  • 单旋情况下:u(叔叔) 不存在 / u(叔叔) 存在且为黑,u(叔叔) 没办法分担只能变色,让 p(父亲) 变黑为顶
  • 双旋情况下:u(叔叔) 不存在 / u(叔叔) 存在且为黑,u(叔叔) 没办法分担只能变色,让 c(新节点) 变黑为顶

2.3 红黑树的插入代码实现

bool Insert(const pair<K, V>& kv) {
    if (_root == nullptr) {
        _root = new Node(kv);
        _root->_col = BLACK;
        return true;
    }
    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;
        }
    }
    cur = new Node(kv);
    cur->_col = RED; // 新节点为红色
    if (parent->_kv.first < kv.first)
        parent->_right = cur;
    else
        parent->_left = cur; // 链接父亲
    cur->_parent = parent;

    // 父亲是红色,出现连续的红色节点(需处理)
    while (parent && parent->_col == RED) {
        // 分两种情况:1.叔叔在左边 2.叔叔在右边
        Node* grandfather = parent->_parent;
        if (parent == grandfather->_left) {
            // 叔叔在右边
            Node* uncle = grandfather->_right;
            if (uncle && uncle->_col == RED) {
                // 叔叔存在且为红色(变色)
                parent->_col = uncle->_col = BLACK;
                grandfather->_col = RED;
                // 继续向上处理,最坏结果处理到根
                cur = grandfather;
                parent = cur->_parent;
            } else {
                // 叔叔不存在,或存在且为黑(旋转 + 变色)
                if (cur == parent->_left) {
                    // c 在父亲左边,构成直线,只单旋一次
                    RotateR(grandfather);
                    parent->_col = BLACK;
                    grandfather->_col = RED;
                } else {
                    // c 在父亲右边,构成折线,需要双旋
                    RotateL(parent);
                    RotateR(grandfather);
                    cur->_col = BLACK;
                    grandfather->_col = RED;
                }
                break;
            }
        } else {
            // 叔叔在左边(类似上列代码)
            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; // _root 节点必为 BLACK
    return true;
}

2.4 查找红黑树

按二叉搜索树逻辑实现即可,搜索效率为 O(logN)

Node* 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.5 遍历打印红黑树

/*public*/
void InOrder() {
    _InOrder(_root);
    cout << endl;
}

/*private*/
void _InOrder(Node* root) {
    if (root == nullptr) {
        return;
    }
    _InOrder(root->_left);
    cout << root->_kv.first << ":" << root->_kv.second << endl;
    _InOrder(root->_right);
}

2.6 红黑树高度及大小

/*public*/
int Size() {
    return _Size(_root);
}
int Height() {
    return _Height(_root);
}

/*private*/
int _Height(Node* root) {
    if (root == nullptr) return 0;
    int leftHeight = _Height(root->_left);
    int rightHeight = _Height(root->_right);
    return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}

int _Size(Node* root) {
    if (root == nullptr) return 0;
    return _Size(root->_left) + _Size(root->_right) + 1;
}

目录

  1. 一、红黑树的概念
  2. 1.1 红黑树的规则(必须牢记)
  3. 1.2 为什么最长路径不超过最短路径 2 倍?
  4. 1.3 红黑树的效率优势
  5. 二、红黑树的实现
  6. 2.1 红黑树的结构定义
  7. 2.2 核心操作:旋转函数
  8. 2.3 核心操作:插入函数
  9. 2.2.2 情况 1:变色
  10. 2.2.3 情况 2:单旋 + 变色
  11. 2.2.4 情况 3:双旋 + 变色
  12. 2.2.5 插入核心问题总结
  13. 2.3 红黑树的插入代码实现
  14. 2.4 查找红黑树
  15. 2.5 遍历打印红黑树
  16. 2.6 红黑树高度及大小

更多推荐文章

查看全部
  • Web 可访问性最佳实践:构建人人可用的前端界面
  • 自然语言处理在金融领域的应用与实战
  • PyCharm 集成 GitHub Copilot 配置指南:学生认证与 2FA
  • 使用 Playwright 封装隐藏自动化特征的 Web 爬虫
  • 单片机开发中C语言为何仍是主流?对比C++生态与效率瓶颈
  • Git 分支管理完全指南:从基础到团队协作
  • 数据结构:顺序表与链表常用算法解析
  • AI 绘画:StableDiffusion 制作赛博机车图教程
  • 火山引擎发布豆包编程模型 Doubao-Seed-Code,支持 Agentic 任务与视觉理解
  • 人形全身 VLA 模型Ψ0:基于人类视频预训练与 MM-DiT 后训练方案
  • AI 入门指南:核心术语解析与常见误区澄清
  • 奈飞工厂算法挑战赛指南
  • SketchUp STL 插件实现 3D 打印模型高效导出
  • Asio C++库核心特性与基础编程模型详解
  • AI Agent 核心概念解析与 LangChain 实战指南
  • C++ 继承机制详解:概念、规则与菱形继承
  • 圣光艺苑:基于 SDXL 的一键鎏金画框生成与提示词指南
  • UI UX Pro Max 驱动的现代前端 UI 工作流指南
  • OpenCode:开源 AI 编程智能体,支持多模型与远程协作
  • 构建 AI 临床副驾驶:基于 Go 的电子病历智能助手与 HIS 对接实战(下)

相关免费在线工具

  • 加密/解密文本

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