【C++进阶篇】红黑树的实现(赋源码)

红黑树:如何用颜色和旋转征服复杂数据

  • 一. 红黑树简介
    • 1.1 基本概念
    • 1.2 红黑树效率
    • 1.3 意义
    • 1.4 应用场景
  • 二. 红黑树实现
    • 2.1 红黑树结构
    • 2.2 插入(难点)
      • 2.2.1 单纯变色的情况
      • 2.2.2 单旋+变色
      • 2.2.3 双旋+变色
    • 2.3 查找
    • 2.4 红黑树的验证
    • 2.5 AVL与RBTree对比
  • 三. 最后

一. 红黑树简介

1.1 基本概念

红黑树是一种自平衡二叉查找树,通过为节点赋予红/黑颜色属性,并遵循五条核心规则(如根节点为黑、红色节点子节点必为黑、任意路径黑色节点数相同等),确保树的高度差不超过两倍,从而在插入、删除、查找操作中维持O(log n)的时间复杂度。其平衡性通过旋转(左旋/右旋)和颜色调整实现,而非严格保持子树高度差,因此平衡维护成本低于AVL树。
在这里插入图片描述
声明:上述树为标准的红黑树,可能有人会思考:叶子结点的孩子为空节点,它不是黑色的呀,这不是违反规则了吗?《算法导论》补充了每个叶子结点(NIL)都是黑色的规则。NIL也会被称为外部节点。
在这里插入图片描述

思考一下:红黑树如何保证最长路径的长度不超过最短路径的2倍???

  1. 极端场景下:从根到NIL节点全是黑色的,该路径就是最短路径,假设最短路径长度为h。
  2. 最优情况下:一个黑节点和一个红节点假设存在h个黑色节点,也存在h个红色节点,红黑成对出现,即最长路径为2h。
  3. 理论上上述两种情景很少见,基本鉴于两者之间,假设任意一条从根到NIL节点路径的长度为x,那么 h <= x < 2h。
  4. 即保证上述结论成立。

如图:
在这里插入图片描述

1.2 红黑树效率

假设N是红黑树中节点数量,h最短路径的长度, 2 h 2^h 2h -1 <= N < 2 2 h 2^{2h} 22h -1,由此推出 h = logN,最长路径是最短路径的两倍,即意味着红黑树增删查改最坏情况下,遍历最深的深度,即2 * logN,时间复杂度为O(logN)。

1.3 意义

  • 红黑树的核心价值在于平衡性能与实现复杂度:
  1. 高效动态操作:相比普通二叉搜索树,避免极端情况下退化为链表;相比AVL树,减少旋转频率,提升插入/删除效率。
  2. 稳定最坏性能:即使频繁更新,仍能保证对数时间复杂度,适合实时系统。
  3. 内存友好:仅需1位存储颜色信息,空间开销小于AVL树的高度位存储。

1.4 应用场景

  • 编程语言基础库:C++的map/set、Java的TreeMap利用红黑树实现有序键值对的高效管理。
  • 操作系统内核:Linux内核用红黑树管理进程调度队列、内存页分配及虚拟文件系统元数据,优化资源分配效率。
  • 数据库与存储:MySQL的InnoDB引擎使用红黑树管理索引B+树的页节点,加速范围查询。
  • 网络协议栈:在需要快速查找与动态更新的场景(如路由表)中,红黑树提供低延迟支持。

二. 红黑树实现

2.1 红黑树结构

//节点颜色
enum Colour
{RED,BLACK
};// 这⾥我们默认按key/value结构实现,三叉链
// 红黑树节点结构
template<class K, class V>
struct RBTreeNode
{// 这⾥更新控制平衡也要加⼊parent指针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
{typedef RBTreeNode<K, V> Node;
public:
private:Node* _root = nullptr;
};

红黑树用三叉链的数据结构实现,红黑树节点中的数据使用pair类型的数据存放,使用pair类型的数据申请红黑树节点。

2.2 插入(难点)

  • 过程:
  1. 如果根节点是空节点,则直接将新插入的节点更新成根节点。
  2. 如果不是,则按照二叉搜索树的规则,插入的key值比根节点大,则往左走;插入的key值比根节点小,则往右走,插入相同的值插入失败,即不允许插入相同的值。然后将插入的节点与父节点进行相连即可。
    **注意:**插入的节点的颜色必须是红色,黑色很难维护,违反规则4。
  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;//插入相同的key值,直接返回false。}}//将新插入的节点与父节点相连//插入黑色节点一定不满足,很难维护,所以咱们插入红色节点cur = new Node(kv);cur->_col = RED;if (parent->_kv.first < kv.first){parent->_left = cur;}else{parent->_right = cur;}cur->_parent = parent;//将当前新插入的节点与parent节点进行相连。//处理不满足红黑树规则的情况,下面继续补充。

2.2.1 单纯变色的情况

下面都是parent都是grandfather的左孩子。

  • 场景1(具体的图):
    在这里插入图片描述
  • 场景1:c是p的左,p是g的左。
    当parent为红,grandfather为黑,且uncle存在且为红。上述图只是一种的情况,无论c是p的左还是右,p是g的左还是右,处理方式一致,如下:将parent和uncle变为黑,grandfather变为红。当parent为黑色即可更新结束;当parent为红色,然后继续向上跟新即可。
  • 场景2:c是p的右,p是g的左。
    在这里插入图片描述
    另外两种不再描述,读者自己想想,锻炼思考能力。
    下面以hb==1,表示存在1个黑色节点的红黑树。
    在这里插入图片描述
    d/e/f为x/y/z/m中任意一种,情况处理一致,将a/b变成黑,c变成红,然后继续向上处理。
  • 扩展处理:
    当hb == (1,2,3,4,5,6,7,8,9,…n)n为自然数,因为d/e/f的情况不需要处理,它本就不违反规则。只需处理插入在a/b任意一个位置,分析是否违反规则即可。

伪代码如下:

Node* grandfather = parent->_parent;
if (grandfather->_left == parent)
{//     g//   p   u//cNode* uncle = grandfather->_right;if (uncle && uncle->_col == RED){//变色parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;//继续向上处理cur = grandfather;parent = cur->_parent;}

当uncle存在且为红时,只需变色即可。

2.2.2 单旋+变色

  • 场景1(uncle不存在):
    在这里插入图片描述
    上述情况单纯变色无法解决问题,以g为旋转点进行右旋,然后将c/g变为红色,p变为黑色。
  • 场景2(uncle存在且为黑,且插入的新节点在p的左边):
    在这里插入图片描述

场景1:以g为旋转点进行右单旋,再把g变为红色,p变为黑色。

2.2.3 双旋+变色

  • 场景3(uncle存在且为黑,插入的新节点位于parent的右)
    在这里插入图片描述

现以parent为旋转点进行左单旋,在意grandfather为旋转点进行右单旋。旋转完成之后跳出循环即可。

  • 伪代码如下:
if (cur == parent->_left)
{RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;
}
else
{//uncle不存在,则c一定是新增节点,假设c不是新增节点,则c一定是黑色,违反规则3//     g//   p    u//     cRotateL(parent);RotateR(grandfather);cur->_col = RED;grandfather->_col = RED;
}break;

parent是grandfather右孩子的处理情况一致。本人就不再重复写了,直接上代码(如下):

else//grandfather右孩子为parent
{//     g//   u   p//cNode* uncle = grandfather->_left;if (uncle && uncle->_col == RED){grandfather->_col = RED;uncle ->_col= parent->_col = BLACK;cur = grandfather;parent = cur->_parent;}else//uncle不存在,或者uncle存在且为黑{//uncle不存在,则c一定是新增节点,假设c不是新增节点,则c一定是黑色,违反规则3//     g黑                    g            p// u黑     p红              u    p       g  c//      c红                         c  uif (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}

整合代码如下:

	while (parent && parent->_col == RED){Node* grandfather = parent->_parent;if (grandfather->_left == parent){//     g//   p   u//cNode* uncle = grandfather->_right;if (uncle && uncle->_col == RED){//变色parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;//继续向上处理cur = grandfather;parent = cur->_parent;}else//uncle不存在,或者uncle存在且为黑{//uncle不存在,则c一定是新增节点,假设c不是新增节点,则c一定是黑色,违反规则3//     g//        p//      c//uncle不存在,或者uncle存在且为黑下面都可以解决上述两个场景if (cur == parent->_left){RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{//uncle不存在,则c一定是新增节点,假设c不是新增节点,则c一定是黑色,违反规则3//     g//   p    u//     cRotateL(parent);RotateR(grandfather);cur->_col = RED;grandfather->_col = RED;}break;}}else//grandfather右孩子为parent{//     g//   u   p//cNode* uncle = grandfather->_left;if (uncle && uncle->_col == RED){grandfather->_col = RED;uncle ->_col= parent->_col = BLACK;cur = grandfather;parent = cur->_parent;}else//uncle不存在,或者uncle存在且为黑{//uncle不存在,则c一定是新增节点,假设c不是新增节点,则c一定是黑色,违反规则3//     g黑                    g            p// u黑     p红              u    p       g  c//      c红                         c  uif (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;
}

2.3 查找

按照二叉搜索树的规则,插入,过程如下:

  • 当前插入的key值比当前节点的key值小,则往左走;比它大,往右走
  • 相等直接返回当前节点的指针,走至空返回false即可。
  • 查询效率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.4 红黑树的验证

思路:以最左路径计算出此路径的黑色节点个数记为h1为参考值,依次递归求其它路径的黑色节点个数,比较各个路径黑色节点个数与h1的比值,发现任意一条路径不同直接返回false即可,所有路径遍历完都相等且任意一条路径的不存在连续的红色节点返回true即可。

  • 伪代码如下:
bool Check(Node* root, int blackNum, int refNum)
{if (root == nullptr){if (blackNum != refNum){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);
}

2.5 AVL与RBTree对比

测试代码:

#include<iostream>
#include<vector>
#include<assert.h>
using namespace std;#include"RBTree.h"
#include"AVLTree.h"// 测试代码
void TestRBTree1()
{RBTree<int, int> t;// 常规的测试用例int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };// 特殊的带有双旋场景的测试用例//int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };for (auto e : a){/*if (e == 14){int x = 0;}*/t.Insert({ e, e });//cout << "Insert:" << e << "->";//cout << t.IsBalanceTree() << endl;}t.InOrder();cout << t.IsBalance() << endl;
}// 插入一堆随机值,测试平衡,顺便测试一下高度和性能等
void TestRBTree2()
{const int N = 10000000;vector<int> v;v.reserve(N);srand(time(0));for (size_t i = 0; i < N; i++){v.push_back(rand() + i);}size_t begin2 = clock();RBTree<int, int> t;for (auto e : v){t.Insert(make_pair(e, e));}size_t end2 = clock();cout << t.IsBalance() << endl;cout << "RBTree Insert:" << end2 - begin2 << endl;cout << "RBTree Height:" << t.Height() << endl;cout << "RBTree Size:" << t.Size() << endl;size_t begin1 = clock();// 确定在的值/*for (auto e : v){t.Find(e);}*/// 随机值for (size_t i = 0; i < N; i++){t.Find((rand() + i));}size_t end1 = clock();cout << "RBTree Find:" << end1 - begin1 << endl << endl;
}void TestAVLTree2()
{const int N = 10000000;vector<int> v;v.reserve(N);srand(time(0));for (size_t i = 0; i < N; i++){v.push_back(rand() + i);}size_t begin2 = clock();AVLTree<int, int> t;for (auto e : v){t.Insert(make_pair(e, e));}size_t end2 = clock();cout << t.IsBalanceTree() << endl;cout << "AVLTree Insert:" << end2 - begin2 << endl;cout << "AVLTree Height:" << t.Height() << endl;cout << "AVLTree Size:" << t.Size() << endl;size_t begin1 = clock();// 确定在的值/*for (auto e : v){t.Find(e);}*/// 随机值for (size_t i = 0; i < N; i++){t.Find((rand() + i));}size_t end1 = clock();cout << "AVLTree Find:" << end1 - begin1 << endl;
}int main()
{TestRBTree2();TestAVLTree2();return 0;
}

运行结果:

1
RBTree Insert:1202
RBTree Height:6
RBTree Size:22
RBTree Find:220
1
AVLTree Insert:2633
AVLTree Height:26
AVLTree Size:6325673
AVLTree
Find:2447

结论:红黑树在插入/删除效率上优于AVL树,而AVL树在查找效率上略胜一筹。

三. 最后

本文阐述了红黑树是一种高效的自平衡二叉搜索树,通过颜色标记和旋转操作维持近似平衡,确保增删查改时间复杂度为O(log n)。其核心规则包括根节点黑、红节点子节点必黑、任意路径黑节点数相同,最长路径不超过最短两倍。相比AVL树,红黑树减少旋转次数,插入/删除效率更高,适合动态数据场景,广泛应用于C++ STL、Linux内核及数据库索引。实现时通过三叉链结构管理节点,插入需处理变色、单旋+变色、双旋+变色三种情况。验证时检查路径黑节点数与连续红色节点。测试表明,红黑树插入效率优于AVL树,而AVL树因严格平衡查找略快,两者均保障了稳定对数级性能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/84097.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【软考向】Chapter 3 数据结构

线性结构线性表顺序存储 —— 访问易&#xff0c;增删难链式存储 —— 访问难、增删易 栈 —— 后进先出 和 队列 —— 先进先出字符串 —— KMP 匹配算法 数组、矩阵和广义表数组 树 —— 树根为第一层&#xff0c;最大层数为树高/深度线索二叉树哈夫曼编码树和森林 —— 树的…

Python 训练 day31

知识点回顾 规范的文件命名规范的文件夹管理机器学习项目的拆分编码格式和类型注解 作业&#xff1a;尝试针对之前的心脏病项目ipynb&#xff0c;将他按照今天的示例项目整理成规范的形式&#xff0c;思考下哪些部分可以未来复用。 机器学习项目的流程 一个典型的机器学习项目通…

C++?模板(进阶)!!!

一、引言 在之前我们已经介绍过C中引入的非常好用的一个工具--模板&#xff0c;同时借助模拟实现string、vector、list等容器熟悉了如何使用模板&#xff0c;今天我们将要一起学习有关模板的进阶知识&#xff0c;如果还想了解模板的概念以及基础的使用可以跳转到以下链接&#…

《量子计算实战》PDF下载

内容简介 在加密、科学建模、制造物流、金融建模和人工智能等领域&#xff0c;量子计算可以极大提升解决问题的效率。量子系统正变得越来越强大&#xff0c;逐渐可用于生产环境。本书介绍了量子计算的思路与应用&#xff0c;在简要说明与量子相关的科学原理之后&#xff0c;指…

Vue3前后端分离用户信息显示方案

在Vue3前后端分离的项目中&#xff0c;若后端仅返回用户ID&#xff0c;可通过以下步骤显示用户名&#xff1a; 解决方案 获取用户信息API 确保后端提供以下任意一种接口&#xff1a; 批量查询接口&#xff1a;传入多个用户ID&#xff0c;返回对应的用户信息列表 单个查询接口…

艾默生流量计与Profibus DP主站转Modbus RTU/TCP网关通讯案例

艾默生流量计与Profibus DP主站转Modbus RTU/TCP网关通讯案例 在现代工业自动化控制系统中&#xff0c;艾默生流量计因其高精度、稳定性和易用性而备受青睐。然而&#xff0c;为了实现与不同协议设备之间的无缝通信&#xff0c;经常需要借助专业的通讯网关进行协议转换。本文将…

SQL优化学习笔记

SQL优化 insert优化 常规优化思路 每次插入数据时都需要和数据库建立连接、关闭连接&#xff0c;因此批量插入会大量节省IO避免浪费。&#xff08;每次500-1000条&#xff09;开启手动提交事务&#xff08;start transaction … commit&#xff09;主键顺序插入&#xff1a;…

50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | Hidden Search Widget (交互式搜索框)

&#x1f4c5; 我们继续 50 个小项目挑战&#xff01;—— Hidden Search Widget 组件 仓库地址&#xff1a;https://github.com/SunACong/50-vue-projects 项目预览地址&#xff1a;https://50-vue-projects.vercel.app/ ✨ 组件目标 点击按钮展开隐藏的搜索框 再次点击按钮…

大模型——多模态检索的RAG系统架构设计

文章目录 1. 系统架构设计核心组件 2. 跨模态向量空间对齐方案方法一&#xff1a;预训练对齐模型&#xff08;如CLIP&#xff09;方法二&#xff1a;跨模态投影网络方法三&#xff1a;联合微调 3. 混合检索策略4. 关键问题解决Q: 如何解决模态间向量尺度不一致&#xff1f;Q: 如…

【NGINX】 -10 keepalived + nginx + httpd 实现的双机热备+ 负载均衡

文章目录 1、主架构图1.1 IP地址规划 2、web服务器操作3、配置nginx服务器的负载均衡4、配置keepalived4.1 master4.1 backup 5、测试双机热备5.1 两台keepalived服务器均开启5.2 模拟master节点故障 1、主架构图 1.1 IP地址规划 服务器IP地址web1192.168.107.193web2192.168.…

SQL 多表关联与分组聚合:解密答题正确率分析

一、问题拆解&#xff1a;从业务需求到SQL逻辑 1.1 需求分析 题目要求&#xff1a;计算浙江大学用户在不同难度题目下的答题正确率&#xff0c;并按正确率升序排序。 关键分析点&#xff1a; 数据来源&#xff1a; user_profile&#xff1a;存储用户信息&#xff08;大学&a…

VS Code启动Git导致大量磁盘读写 - 解决方案

问题 通过VS Code打开项目后&#xff0c;若项目使用了Git&#xff0c;且文件数目较多&#xff0c;则VS Code会自动在后台调用Git检查项目修改&#xff0c;会造成大量磁盘读写&#xff0c;严重影响电脑性能。 解决方案 在VS Code设置中关闭Git功能&#xff0c;在终端中使用Gi…

Vue3 与 Vue2 区别

一、Vue3 与 Vue2 区别 对于生命周期来说&#xff0c;整体上变化不大&#xff0c;只是大部分生命周期钩子名称上 “on”&#xff0c;功能上是类似的。不过有一点需要注意&#xff0c;组合式API的Vue3 中使用生命周期钩子时需要先引入&#xff0c;而 Vue2 在选项API中可以直接…

网络安全之身份验证绕过漏洞

漏洞简介 CrushFTP 是一款由 CrushFTP LLC 开发的强大文件传输服务器软件&#xff0c;支持FTP、SFTP、HTTP、WebDAV等多种协议&#xff0c;为企业和个人用户提供安全文件传输服务。近期&#xff0c;一个被编号为CVE-2025-2825的严重安全漏洞被发现&#xff0c;该漏洞影响版本1…

Word2Vec模型学习和Word2Vec提取相似文本体验

文章目录 说明Word2Vec模型核心思想两种经典模型关键技术和算法流程优点和局限应用场景 Word2Vec提取相似文本完整源码执行结果 说明 本文适用于初学者&#xff0c;体验Pytorch框架在自然语言处理中的使用。简单了解学习Word2Vec模型&#xff0c;体验其使用。 Word2Vec模型 …

flutter 配置 安卓、Ios启动图

android 配置启动图 launch_background.xml <?xml version"1.0" encoding"utf-8"?> <!-- Modify this file to customize your launch splash screen --> <layer-list xmlns:android"http://schemas.android.com/apk/res/android&…

MCP(一)——QuickStart

目录 1. MCP简介2. MCP的优势3. MCP核心4. QuickStart For Server Developers(仅具参考)4.1 MCP核心概念4.2 构建MCP服务器的代码4.2.1 设置MCP服务器实例4.2.2 辅助函数4.2.3 实现工具执行4.2.4 在Cherry-Studio中添加MCP服务器4.2.5 演示4.2.5.1 测试工具get_alerts4.2.5.2 测…

Nginx网站功能

一.基于授权的访问控制 1.基于授权的访问控制简介 Nginx与Apahce 一样&#xff0c;可以实现基于用户授权的访问控制&#xff0c;当客户端想要访问相应网站或者目录时&#xff0c;要求用户输入用户名和密码才能正常访问&#xff0c;配置步骤与Apache基本一致。 2.基于授权的访…

海外盲盒系统开发:重构全球消费体验的科技引擎

当盲盒文化席卷全球&#xff0c;海外盲盒系统开发已成为重构消费体验的核心赛道。数据显示&#xff0c;2025年全球盲盒市场规模突破120亿&#xff0c;东南亚市场年增长率达4540。我们开发的海外盲盒系统&#xff0c;以技术创新为驱动&#xff0c;打造覆盖全链路的全球化解决方案…

数学建模初等模型应用

一、目的 掌握初等模型的建模方法,对简单的初等模型能借助Matlab工具软件进行辅助建模、求解和检验。 二、实验内容与设计思想&#xff08;设计思路、主要代码分析&#xff09; 1、预测鱼的质量 &#xff08;1&#xff09;设计思路&#xff1a;使用线性回归模型预测鱼的质量…