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

C++ 红黑树设计与实现详解

综述由AI生成红黑树是一种自平衡二叉搜索树,通过颜色标记和旋转操作保证最长路径不超过最短路径的两倍。详细讲解了红黑树的五条规则、插入算法的三种调整情况(变色、单旋、双旋)以及验证方法。相较于 AVL 树,红黑树在插入删除时旋转次数更少,更适合频繁变动的数据场景。文中提供了完整的 C++ 实现代码,涵盖节点结构、查找、插入及平衡性校验逻辑,适合希望深入理解平衡树机制的开发者阅读。

KernelLab发布于 2026/3/15更新于 2026/5/48 浏览
C++ 红黑树设计与实现详解

C++ 红黑树设计与实现详解

前言

在深入平衡二叉搜索树的学习中,AVL 树让我们理解了高度平衡的重要性。而红黑树(Red-Black Tree)则是另一种更为实用且高效的平衡方案,它是 C++ STL 中 map 和 set 等容器的底层实现核心。相比 AVL 树严格的平衡要求,红黑树通过颜色标记来近似平衡,牺牲了部分查询效率换取了更少的旋转操作,从而在插入和删除场景下表现更佳。

1. 红黑树的概念

1.1 什么是红黑树

红黑树是一种自平衡的二叉搜索树。除了每个节点存储键值对外,它还增加了一个颜色属性,节点颜色只能是红色或黑色。通过对路径上节点颜色的约束,确保没有一条路径会比其他路径长出两倍,从而实现近似平衡。

1.2 红黑树的五条规则

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

注:这里的叶子节点通常指空指针,而非传统意义上的终端节点。《算法导论》中常将 NULL 视为外部节点。

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

根据规则 4,每条路径上的黑色节点数量(黑高 bh)是固定的。最短路径即为全黑节点路径,长度为 bh。由于规则 3 限制了红色节点不能连续出现,最长路径必然是红黑交替,长度约为 2bh。因此,红黑树的最长路径不会超过最短路径的两倍。

1.4 效率分析

假设节点数为 N,最短路径长度为 h。由 2^h - 1 <= N < 2^(2h) - 1 可知,h 约等于 logN。因此,红黑树的查找、插入、删除时间复杂度均为 O(logN)。虽然比 AVL 树略慢,但插入时的旋转次数更少,整体性能更优。

2. 红黑树的实现

2.1 节点结构

红黑树采用三叉链表结构,每个节点需要记录父节点、左右孩子以及颜色信息。默认新插入节点为红色。

enum Colour { RED, BLACK };

template<class K, class V>
struct RBTreeNode {
    pair<K, V> _kv;
    RBTreeNode* _left;
    RBTreeNode* _right;
    RBTreeNode* _parent;
    Colour _col;

    RBTreeNode(const pair<K, V>& kv)
        :_kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr), _col(RED) {}
};

2.2 查找操作

红黑树的查找逻辑与二叉搜索树完全一致,利用 Key 进行比较,时间复杂度 O(logN)。

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. 红黑树的插入

插入是红黑树最复杂的部分。插入后需调整以满足红黑性质。新节点默认为红色,若父节点为黑色则无需调整;若父节点为红色,则违反规则 3,需进行变色或旋转。

设当前节点为 cur,父节点为 p,祖父节点为 g,叔叔节点为 u。

3.1 插入流程概览

  1. 若树为空,直接插入黑色根节点。
  2. 若树非空,按 BST 规则找到位置插入红色节点。
  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->_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 为红,g 为黑,u 为红。策略是将 p 和 u 变为黑色,g 变为红色。然后将 g 视为新的 cur 继续向上检查。若 g 为根节点,最后将其染黑。

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

3.3 情况二:叔叔节点不存在或为黑色(单旋 + 变色)

当 u 为黑或不存在时,需结合旋转。若 cur 和 p 同侧(如左左),以 g 为轴右旋,变色后结束;若不同侧(如左右),先左旋再右旋。

左左情况(右单旋):

if (parent->_left == cur) {
    RotateR(grandfather);
    parent->_col = BLACK;
    grandfather->_col = RED;
    break;
}

3.4 情况三:双旋 + 变色

当 cur 和 p 异侧时(如 p 是 g 左,cur 是 p 右),需先对 p 左旋,再对 g 右旋,最后变色。

RotateL(parent);
RotateR(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;

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 << "连续红色节点" << endl;
        return false;
    }
    if (root->_col == BLACK) {
        blackNum++;
    }
    return Check(root->_left, blackNum, refNum) && 
           Check(root->_right, blackNum, refNum);
}

5. 关于删除

红黑树的删除逻辑比插入更为复杂,涉及多种旋转和颜色调整情况。本文主要聚焦于插入实现,删除操作建议参考专业文献或后续深入学习。

6. 完整代码

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

namespace wang {
using namespace std;

enum Colour { RED, BLACK };

template<class K, class V>
struct RBTreeNode {
    pair<K, V> _kv;
    RBTreeNode* _left;
    RBTreeNode* _right;
    RBTreeNode* _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) 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) {
            _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) {
            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);
    }

    bool IsBalance() {
        if (!_root) 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);
    }

protected:
    int _Size(Node* root) {
        if (!root) return 0;
        return _Size(root->_left) + _Size(root->_right) + 1;
    }
    int _Height(Node* root) {
        if (!root) return 0;
        int l = _Height(root->_left);
        int r = _Height(root->_right);
        return l > r ? l + 1 : r + 1;
    }

private:
    Node* _root = nullptr;
};
}

7. 小结

红黑树通过颜色约束和旋转操作维持平衡,是工程实践中非常高效的数据结构。掌握其插入原理有助于深入理解 STL 容器及底层算法设计。后续可进一步研究删除操作的复杂性。

目录

  1. C++ 红黑树设计与实现详解
  2. 前言
  3. 1. 红黑树的概念
  4. 1.1 什么是红黑树
  5. 1.2 红黑树的五条规则
  6. 1.3 为什么最长路径不超过最短路径的两倍
  7. 1.4 效率分析
  8. 2. 红黑树的实现
  9. 2.1 节点结构
  10. 2.2 查找操作
  11. 3. 红黑树的插入
  12. 3.1 插入流程概览
  13. 3.2 情况一:叔叔节点存在且为红色
  14. 3.3 情况二:叔叔节点不存在或为黑色(单旋 + 变色)
  15. 3.4 情况三:双旋 + 变色
  16. 4. 红黑树的验证
  17. 5. 关于删除
  18. 6. 完整代码
  19. 7. 小结
  • 💰 8折买阿里云服务器限时8折了解详情
  • GPT-5.5 超高智商模型1元抵1刀ChatGPT中转购买
  • 代充Chatgpt Plus/pro 帐号了解详情
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

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

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

更多推荐文章

查看全部
  • C++ 红黑树核心原理与实战实现
  • 计算机视觉高级应用与前沿技术实战解析
  • 前端面试核心知识点与八股文整理
  • Mac Mini M4 本地运行大模型:Ollama、Llama 与 ComfyUI 部署指南
  • 金仓数据库 SQL 防火墙:原理、优势与配置
  • Llama-Factory 训练进度条卡住?排查与优化指南
  • 机器人动态控制:重力补偿技术实战指南
  • MySQL 环境配置实战:CentOS 7 与 Ubuntu 双系统安装指南
  • 当人人都会用AI,你靠什么脱颖而出?
  • Python RDKit 化学信息学工具库使用指南
  • Spring Boot 缓存与性能优化
  • DeepSeek-R1-Distill-Llama-8B 部署:Docker Compose 推理服务
  • 机器人 DH 参数模型与正运动学
  • Ubuntu 22.04 离线部署 Qwen3-4B 模型:vLLM 与 Docker 多卡配置指南
  • C++ 伸展树与红黑树原理及实现
  • ComfyUI Photoshop插件完整教程:5步实现AI绘画工作流
  • 内容生成模式解析:UGC、PGC、PUGC、OGC、MGC、BGC 与 AIGC
  • GitHub 开源免费 PDF 编辑器推荐:告别破解,高效编辑
  • AIOps 实践:基于 Dify+LangBot 实现飞书智能体对话机器人
  • OpenClaw 开源 AI 智能体:核心原理、功能特性与本地部署

相关免费在线工具

  • 加密/解密文本

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