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

C++ 红黑树核心原理与插入实现详解

红黑树是一种自平衡二叉搜索树,通过颜色约束保证最长路径不超过最短路径的两倍。其核心在于维护五条性质,特别是红色节点不相邻及黑色节点高度一致。插入操作需处理三种情况:叔叔节点为红时变色;叔叔为黑且同侧时单旋变色;叔叔为黑且异侧时双旋变色。详细解析了红黑树的结构定义、插入逻辑及验证方法,提供了完整的 C++ 实现代码。

1739658202发布于 2026/3/21更新于 2026/7/2129 浏览
C++ 红黑树核心原理与插入实现详解

C++ 红黑树设计与实现

1. 红黑树的概念

1.1 什么是红黑树

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

它是一棵被严格规则束缚的二叉搜索树,这些规则保证了树的相对平衡性。下面我们来详细看看这些规则。

1.2 红黑树的五条性质

  1. 每个节点要么是红色,要么是黑色。
  2. 根节点必须是黑色。
  3. 如果一个节点是红色的,则它的两个子节点都必须是黑色的(即不能有两个连续的红色节点)。
  4. 对任意节点而言,从该节点到其所有后代叶子节点的简单路径上,均包含相同数目的黑色节点。
  5. 所有的叶子节点(NULL 指针)都是黑色的。

注意:这里的'叶子节点'通常指 NULL 指针,有些书籍称之为外部节点。引入 NULL 节点是为了方便准确地标记路径边界。

红黑树示例

红黑树示例

红黑树示例

1.3 为什么最长路径不超过最短路径的两倍

根据性质 4,每条路径上的黑色节点数量是固定的,记为 bh(Black Height)。

  • 最短路径:全由黑色节点组成,长度为 bh。
  • 最长路径:在满足性质 3(无连续红节点)的前提下,红黑交替出现,长度最多为 2 * bh。

因此,红黑树的最长路径不会超过最短路径的两倍,这保证了树的高度约为 log N。

1.4 效率分析

假设节点数为 N,高度为 h。由于 2^h - 1 <= N < 2^(2h) - 1,可推导出 h ≈ log N。这意味着红黑树的查找、插入和删除操作的时间复杂度均为 O(log N)。

相比于 AVL 树,红黑树对平衡性的要求稍宽松,因此在插入和删除时旋转次数更少,整体性能更稳定。

2. 红黑树的实现

2.1 节点结构

在编写代码前,我们需要定义节点结构。红黑树通常采用三叉链表,节点需包含父节点指针、左右孩子指针以及颜色属性。我们使用 Key-Value 结构存储数据,Key 用于比较大小,Value 为实际数据。

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 {
    typedef RBTreeNode<K, V> Node;
private:
    Node* _root = nullptr;
    // ... 后续方法
};

2.2 查找操作

红黑树的查找逻辑与普通二叉搜索树一致,时间复杂度为 O(log N)。

Node* Find(const K& key) {
    Node* cur = _root;
    while (cur) {
        if (cur->_kv.first > key) {
            cur = cur->_left;
        } else if (cur->_kv.first < key) {
            cur = cur->_right;
        } else {
            return cur;
        }
    }
    return nullptr;
}

3. 红黑树的插入

插入是红黑树中最复杂的部分之一。新节点默认插入为红色,以不破坏性质 4(黑色节点高度)。如果插入后违反性质 3(双红冲突),则需要通过变色和旋转来修复。

3.1 插入流程概览

  1. 若树为空,直接插入黑色根节点。
  2. 若树非空,按 BST 规则找到位置,插入红色节点。
  3. 检查是否违反性质 3(父节点为红色)。
    • 若父节点为黑色,无需调整。
    • 若父节点为红色,需查看叔叔节点(Uncle)的颜色及位置关系,分情况处理。
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->_left;
        } else if (cur->_kv.first < kv.first) {
            parent = cur;
            cur = cur->_right;
        } else {
            return false; // 重复节点
        }
    }

    cur = new Node(kv);
    cur->_col = RED; // 新节点默认为红
    cur->_parent = parent;

    if (parent->_kv.first < kv.first) {
        parent->_right = cur;
    } else {
        parent->_left = cur;
    }

    // 开始调整平衡
    while (parent && parent->_col == RED) {
        Node* grandfather = parent->_parent;
        // ... 具体调整逻辑
    }

    _root->_col = BLACK; // 确保根节点为黑
    return true;
}

3.2 情况一:叔叔节点为红色(变色)

当当前节点 cur、父节点 p 均为红色,且叔叔节点 u 存在且为红色时,说明祖父节点 g 必然为黑色。此时将 p 和 u 变为黑色,g 变为红色,并将 g 视为新的当前节点继续向上检查。

若 g 变为根节点,则必须将其染回黑色。

while (parent && parent->_col == RED) {
    Node* grandfather = parent->_parent;
    if (grandfather->_left == parent) {
        Node* uncle = grandfather->_right;
        if (uncle && uncle->_col == RED) {
            // 变色
            grandfather->_col = RED;
            parent->_col = BLACK;
            uncle->_col = BLACK;
            cur = grandfather;
            parent = cur->_parent;
        }
    } else {
        Node* uncle = grandfather->_left;
        if (uncle && uncle->_col == RED) {
            grandfather->_col = RED;
            parent->_col = BLACK;
            uncle->_col = BLACK;
            cur = grandfather;
            parent = cur->_parent;
        }
    }
}

3.3 情况二:叔叔节点为黑色 + 同侧(单旋 + 变色)

当叔叔节点不存在或为黑色,且当前节点与父节点位于同一侧(如左左或右右)时,进行单旋并交换颜色。

  • 左左:以祖父为轴右旋,父变黑,祖父变红。
  • 右右:以祖父为轴左旋,父变黑,祖父变红。
if (grandfather->_left == parent) {
    Node* uncle = grandfather->_right;
    if (!uncle || uncle->_col == BLACK) {
        if (parent->_left == cur) {
            // 左左 -> 右旋
            RotateR(grandfather);
            parent->_col = BLACK;
            grandfather->_col = RED;
        } else {
            // 左右 -> 双旋
            RotateL(parent);
            RotateR(grandfather);
            cur->_col = BLACK;
            grandfather->_col = RED;
        }
        break;
    }
} else {
    Node* uncle = grandfather->_left;
    if (!uncle || uncle->_col == BLACK) {
        if (parent->_right == cur) {
            // 右右 -> 左旋
            RotateL(grandfather);
            parent->_col = BLACK;
            grandfather->_col = RED;
        } else {
            // 右左 -> 双旋
            RotateR(parent);
            RotateL(grandfather);
            cur->_col = BLACK;
            grandfather->_col = RED;
        }
        break;
    }
}

3.4 情况三:叔叔节点为黑色 + 异侧(双旋 + 变色)

当叔叔节点为黑色,且当前节点与父节点位于异侧(如左右或右左)时,先对父节点进行单旋,再对祖父节点进行反向单旋,最后变色。

  • 左右:先左旋父,再右旋祖父,当前节点变黑,祖父变红。
  • 右左:先右旋父,再左旋祖父,当前节点变黑,祖父变红。

(注:上述代码片段已涵盖此逻辑)

4. 红黑树的验证

为了确认实现正确,需要验证四条核心性质。重点在于检查黑色节点高度是否一致,以及是否存在连续红色节点。

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);
}

bool IsBalance() {
    if (_root == nullptr) return true;
    if (_root->_col == RED) return false;

    int refNum = 0;
    Node* cur = _root;
    while (cur) {
        if (cur->_col == BLACK) ++refNum;
        cur = cur->_left;
    }
    return Check(_root, 0, refNum);
}

5. 关于删除

红黑树的删除操作比插入更为复杂,涉及多种旋转和变色组合。由于篇幅限制,本文主要聚焦于插入机制的核心逻辑,这是理解红黑树平衡原理的关键基础。

6. 完整代码实现

以下是整合后的完整类定义,包含旋转、插入、查找及验证功能。

#pragma once
#include<iostream>
#include<vector>
#include<assert.h>
#include<ctime>

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 {
    typedef RBTreeNode<K, V> Node;
private:
    void RotateR(Node* parent) {
        Node* subl = parent->_left;
        Node* sublR = subl->_right;
        parent->_left = sublR;
        if (sublR) sublR->_parent = parent;

        Node* ppnode = parent->_parent;
        subl->_right = parent;
        parent->_parent = subl;

        if (ppnode == nullptr) {
            _root = subl;
            subl->_parent = nullptr;
        } else {
            if (subl->_kv.first > ppnode->_kv.first) {
                ppnode->_right = subl;
            } else {
                ppnode->_left = subl;
            }
            subl->_parent = ppnode;
        }
    }

    void RotateL(Node* parent) {
        Node* subL = parent->_right;
        Node* subLL = subL->_left;
        Node* ppnode = parent->_parent;
        parent->_right = subLL;
        if (subLL) subLL->_parent = parent;

        subL->_left = parent;
        parent->_parent = subL;

        if (ppnode == nullptr) {
            _root = subL;
            subL->_parent = nullptr;
        } else {
            if (subL->_kv.first > ppnode->_kv.first) {
                ppnode->_right = subL;
            } else {
                ppnode->_left = subL;
            }
            subL->_parent = ppnode;
        }
    }

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

public:
    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->_left;
            } else if (cur->_kv.first < kv.first) {
                parent = cur;
                cur = cur->_right;
            } else {
                return false;
            }
        }

        cur = new Node(kv);
        cur->_col = RED;
        cur->_parent = parent;

        if (parent->_kv.first < kv.first) {
            parent->_right = cur;
        } else {
            parent->_left = cur;
        }

        while (parent && parent->_col == RED) {
            Node* grandfather = parent->_parent;
            if (grandfather->_left == parent) {
                Node* uncle = grandfather->_right;
                if (uncle && uncle->_col == RED) {
                    grandfather->_col = RED;
                    parent->_col = BLACK;
                    uncle->_col = BLACK;
                    cur = grandfather;
                    parent = cur->_parent;
                } else {
                    if (parent->_left == cur) {
                        RotateR(grandfather);
                        parent->_col = BLACK;
                        grandfather->_col = RED;
                    } else {
                        RotateL(parent);
                        RotateR(grandfather);
                        cur->_col = BLACK;
                        grandfather->_col = RED;
                    }
                    break;
                }
            } else {
                Node* uncle = grandfather->_left;
                if (uncle && uncle->_col == RED) {
                    grandfather->_col = RED;
                    parent->_col = BLACK;
                    uncle->_col = BLACK;
                    cur = grandfather;
                    parent = cur->_parent;
                } else {
                    if (parent->_right == cur) {
                        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;
    }

    Node* Find(const K& key) {
        Node* cur = _root;
        while (cur) {
            if (cur->_kv.first > key) {
                cur = cur->_left;
            } else if (cur->_kv.first < key) {
                cur = cur->_right;
            } else {
                return cur;
            }
        }
        return nullptr;
    }

    void InOrder() {
        _InOrder(_root);
    }

    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);
    }

    bool IsBalance() {
        if (_root == nullptr) return true;
        if (_root->_col == RED) return false;
        int refNum = 0;
        Node* cur = _root;
        while (cur) {
            if (cur->_col == BLACK) ++refNum;
            cur = cur->_left;
        }
        return Check(_root, 0, refNum);
    }

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

protected:
    int _Size(Node* root) {
        if (root == nullptr) return 0;
        return _Size(root->_left) + _Size(root->_right) + 1;
    }
    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;
    }

private:
    Node* _root = nullptr;
};

7. 小结

红黑树通过严格的颜色约束实现了高效的自平衡。虽然删除操作较为复杂,但掌握插入过程中的三种调整情况(变色、单旋、双旋)足以理解其核心平衡机制。在实际开发中,STL 的 map 和 set 底层正是基于红黑树实现的,理解其原理有助于更好地利用标准库容器。

目录

  1. C++ 红黑树设计与实现
  2. 1. 红黑树的概念
  3. 1.1 什么是红黑树
  4. 1.2 红黑树的五条性质
  5. 1.3 为什么最长路径不超过最短路径的两倍
  6. 1.4 效率分析
  7. 2. 红黑树的实现
  8. 2.1 节点结构
  9. 2.2 查找操作
  10. 3. 红黑树的插入
  11. 3.1 插入流程概览
  12. 3.2 情况一:叔叔节点为红色(变色)
  13. 3.3 情况二:叔叔节点为黑色 + 同侧(单旋 + 变色)
  14. 3.4 情况三:叔叔节点为黑色 + 异侧(双旋 + 变色)
  15. 4. 红黑树的验证
  16. 5. 关于删除
  17. 6. 完整代码实现
  18. 7. 小结
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

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

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

更多推荐文章

查看全部
  • Spring Web MVC 入门:从概念到实践
  • Python RPA+ 爬虫:模拟人工操作采集 ERP 系统数据(金蝶/用友无接口)
  • Spring Boot 入门:Spring Web MVC 核心概念与实战解析
  • 在 Qt Creator 里启用 GitHub Copilot
  • Flutter eth_sig_util 在鸿蒙端的适配与 Web3 签名实战
  • Gitee 代码上传实战:Git 基础与推送流程详解
  • 周鸿祎:大模型时代的机会在企业级市场
  • Ubuntu Linux 部署 Kubernetes 集群实战指南
  • 微信小程序基础组件概览
  • Python 开发者为何开始转向学习 Rust
  • Git-AI:追踪 AI 生成代码的 Git 扩展工具
  • Whisper-large-v3-turbo 语音识别模型速度优化技术解析
  • AionUi:开源本地AI协作平台
  • Python 常用数据结构:列表(List)基础用法与操作详解
  • OpenClaw 记忆系统实战:Token 压缩与自我反思机制
  • OpenClaw 多 Agent 与多飞书机器人配置实践
  • 基于魔搭社区免费 GPU 使用 LLaMaFactory 微调大模型
  • gpt-oss-20b WEBUI 部署与使用全流程指南
  • Rokid SLAM 算法深度剖析:从传感器融合到空间重建
  • Claude Code 源码因 Source Map 配置失误泄露事件复盘

相关免费在线工具

  • 加密/解密文本

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