c++进阶之------红黑树
一、概念
红黑树(Red-Black Tree)是一种自平衡二叉查找树,它在计算机科学的许多领域中都有广泛应用,比如Java中的TreeMap和C++中的set/map等数据结构的底层实现。红黑树通过在每个节点上增加一个颜色属性(红色或黑色),并遵循一定的规则来确保树的平衡性,从而保证了各项操作的时间复杂度为O(log n)。
二、性质
红黑树具有以下性质:
-
每个节点是红色或黑色。
-
根节点是黑色。
-
所有叶子节点(NIL节点)是黑色。
-
如果一个节点是红色,则它的两个子节点都是黑色。(注:这条规则保证了从任一节点到其每个叶子节点的所有路径上不会出现两个连续的红色节点)
-
从任一节点到其每个叶子节点的所有路径都包含相同数目的黑色节点。
三、构建红黑树
3.1 树的基本结构
#pragma once// 枚举值表示颜色
enum color
{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;color _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;
};
3.2 树的插入
在红黑树中插入一个节点后,可能会破坏红黑树的性质,因此需要通过旋转和重新着色来恢复红黑树的平衡。插入操作的具体步骤如下:
-
插入节点:按照普通二叉查找树的插入方式插入新节点,并将新节点着色为红色。
-
检查平衡性:检查插入新节点后是否破坏了红黑树的性质。如果插入的新节点是根节点,则直接将其着色为黑色;如果新节点的父节点是黑色,则红黑树的性质未被破坏,插入结束;如果新节点的父节点是红色,则需要进行修复。
-
修复红黑树性质:根据新节点的父节点和叔叔节点的颜色以及它们的位置关系,分为以下几种情况:
-
情况一:叔叔节点是红色:将父节点和叔叔节点着色为黑色,祖父节点着色为红色,然后将祖父节点作为新的当前节点,继续检查。

-
情况二:叔叔节点是黑色,且当前节点是父节点的左孩子:此时要进行旋转来解决,单纯的变色已经不能解决问题了,以爷爷结点为基准右旋之后在变色
-
情况三:叔叔节点是黑色,且当前节点是父节点的右孩子:此时要进行左右双旋转,具体参见代码
-
注:情况123均需要考虑u p 还是p u 的问题!!!
代码显示:
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);//要是插入黑节点会导致性质5不符合,所以插红的cur->_col = RED;//考虑做左孩子还是右孩子if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent; //别忘了把_parent给到parent,否则下文的parent还是空!//由于我插入的结点必须为红,而红黑树又要求不能有两个连着的红结点//所以我们要判断一下父节点的颜色while (parent && parent->_col == RED){//父节点为红色Node* grandfather = parent->_parent;if (parent == grandfather->_left) //parent为左孩子 {// g 黑// p u 红 红Node* uncle = grandfather->_right;// 叔叔存在且为红if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK; //g和u都为红,那么一起变色就o而k之grandfather->_col = RED;// 继续往上处理cur = grandfather;parent = cur->_parent;}else{// 叔叔不存在或者存在且为黑// g 黑// p u 红 黑//c 红if (cur == parent->_left){RotateR(grandfather); //单纯变色无法解决,旋转一下parent->_col = BLACK;grandfather->_col = RED;}else{// g// p u// cRotateL(parent);RotateR(grandfather);grandfather->_col = RED;cur->_col = BLACK;}break; //处理的是一个子树,这个符合了上面的也就Ok了}} else //parent为右孩子{// g 黑// u p 红 红Node* uncle = grandfather->_left;// 叔叔存在且为红,-》变色即可if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;// 继续往上处理cur = grandfather;parent = cur->_parent;}else{// 叔叔不存在或者存在且为黑// // 旋转+变色// g 黑// u p 黑 红// c 红if (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{// g 黑// u p 黑 红// c 红RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return true;
}
3.3 遍历红黑树
我们还是使用中序遍历遍历红黑树,代码如下:
void InOrder(){_InOrder(_root);cout << endl;}void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_kv.first << " ";_InOrder(root->_right);}
3.4 检查红黑树
检查的方法很简单,就是通过随便跑一条路,数一下黑色节点,并将其作为参考值,之后在遍历整棵树,看看是不是每条路上的黑色节点数都是等于参考值的,换句话说,如果我们的树满足红黑树的性质,那么就o而k之,否则就不o而k之~
// 前序递归遍历bool Check(Node* root, int blacknum, const int refnum){if (root == nullptr){// 前序遍历走到空时,意味着一条路径走完了//cout << blackNum << endl;if (refnum != blacknum){cout << "存在黑色结点的数量不相等的路径" << endl;return false;}return true;}// 检查孩子不太方便,因为孩子有两个,且不一定存在,反过来检查父亲就方便多了if (root->_col == RED && root->_parent && 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 IsBalanceTree(){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);}
3.5 代码测试
#include<iostream>
#include<vector>
using namespace std;#include"RBTree.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){t.Insert({ e, e });}t.InOrder();cout << t.IsBalanceTree() << 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);}RBTree<int, int> t;for (size_t i = 0; i < v.size(); ++i){t.Insert(make_pair(v[i], v[i]));}cout << t.IsBalanceTree() << endl;
}int main()
{TestRBTree1();TestRBTree2();return 0;
}
3.6 代码汇总
这里只放.h文件的!
#pragma once
#include<iostream>
#include<utility>
using namespace std;
// 枚举值表示颜色
enum color
{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;color _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: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);//要是插入黑节点会导致性质5不符合,所以插红的cur->_col = RED;//考虑做左孩子还是右孩子if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent; //别忘了把_parent给到parent,否则下文的parent还是空!//由于我插入的结点必须为红,而红黑树又要求不能有两个连着的红结点//所以我们要判断一下父节点的颜色while (parent && parent->_col == RED){//父节点为红色Node* grandfather = parent->_parent;if (parent == grandfather->_left) //parent为左孩子 {// g 黑// p u 红 红Node* uncle = grandfather->_right;// 叔叔存在且为红if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK; //g和u都为红,那么一起变色就o而k之grandfather->_col = RED;// 继续往上处理cur = grandfather;parent = cur->_parent;}else{// 叔叔不存在或者存在且为黑// g 黑// p u 红 黑//c 红if (cur == parent->_left){RotateR(grandfather); //单纯变色无法解决,旋转一下parent->_col = BLACK;grandfather->_col = RED;}else{// g// p u// cRotateL(parent);RotateR(grandfather);grandfather->_col = RED;cur->_col = BLACK;}break; //处理的是一个子树,这个符合了上面的也就Ok了}} else //parent为右孩子{// g 黑// u p 红 红Node* uncle = grandfather->_left;// 叔叔存在且为红,-》变色即可if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;// 继续往上处理cur = grandfather;parent = cur->_parent;}else{// 叔叔不存在或者存在且为黑// // 旋转+变色// g 黑// u p 黑 红// c 红if (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{// g 黑// u p 黑 红// c 红RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return true;}void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;subL->_right = parent;if (subLR)subLR->_parent = parent;Node* parentParent = parent->_parent;parent->_parent = subL;//if (parentParent == nullptr)if (parent == _root){_root = subL;subL->_parent = nullptr;}else{if (parentParent->_left == parent){parentParent->_left = subL;}else{parentParent->_right = subL;}subL->_parent = parentParent;}}void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;Node* parentParent = parent->_parent;subR->_left = parent;parent->_parent = subR;if (parentParent == nullptr){_root = subR;subR->_parent = nullptr;}else{if (parent == parentParent->_left){parentParent->_left = subR;}else{parentParent->_right = subR;}subR->_parent = parentParent;}}void InOrder(){_InOrder(_root);cout << endl;}void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_kv.first << " ";_InOrder(root->_right);}// 前序递归遍历bool Check(Node* root, int blacknum, const int refnum){if (root == nullptr){// 前序遍历走到空时,意味着一条路径走完了//cout << blackNum << endl;if (refnum != blacknum){cout << "存在黑色结点的数量不相等的路径" << endl;return false;}return true;}// 检查孩子不太方便,因为孩子有两个,且不一定存在,反过来检查父亲就方便多了if (root->_col == RED && root->_parent && 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 IsBalanceTree(){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);}
private:Node* _root = nullptr;
};
四、总结及应用
在红黑树中,我们省去了ALV树的平衡因子,而换用颜色来判断树的平衡,这样使得代码的逻辑变得简单明了 ,后续我们对map和set的封装(实现mymap和myset)便会用到红黑树的思想与结构!
红黑树通过其自平衡的特性,确保了在插入、删除和查找操作中的高效性,使其成为许多实际应用中不可或缺的数据结构。
相关文章:
c++进阶之------红黑树
一、概念 红黑树(Red-Black Tree)是一种自平衡二叉查找树,它在计算机科学的许多领域中都有广泛应用,比如Java中的TreeMap和C中的set/map等数据结构的底层实现。红黑树通过在每个节点上增加一个颜色属性(红色或黑色&am…...
《鸿蒙原生应用开发:掌控Ability生命周期的艺术》
在鸿蒙原生应用开发的广袤天地中,Ability作为构建应用的基本单元,其生命周期的有效管理宛如基石之于高楼,是打造稳定、高效且用户体验卓越应用的关键所在。随着鸿蒙生态的蓬勃发展,深入理解并巧妙运用Ability生命周期,…...
ubuntu22.04安装搜狗输入法保姆教程~
一、添加中文语言支持 1.首先打开设置,找到Language and Region 2.点击Manage Installed Languages 3.点击 Install/Remove Languages... 4.选中Chinese (simplified),点击Apply...
《数据库原理》SQLServer期末复习_题型+考点
目录 题型: 一. 概况分析题(5小题,每小题2分,共10分) 二. 计算题(3小题,每小题5分,共15分) 三. 数据库设计(2小题,每小题10分,共2…...
Zstd(Zstandard)压缩算法
要压缩的数据量越小,压缩的难度就越大。这个问题对所有压缩算法都是通用的,原因是压缩算法从过去的数据中学习如何压缩未来的数据。但是,在新数据集开始时,没有“过去”可以构建。 官网 为了解决这种情况,Zstd 提供了一…...
烧结银技术赋能新能源汽车超级快充与高效驱动
烧结银技术赋能新能源汽车超级快充与高效驱动 在新能源汽车领域,高压快充技术的突破与高功率密度驱动系统的创新正成为行业竞争的焦点。比亚迪于 2025 年发布的超级 e 平台,通过整合全域千伏高压架构、兆瓦级闪充技术及碳化硅(SiC࿰…...
本地部署 browser-use
本地部署 browser-use 0. 引言1. 核心功能与优势2. 快速上手3. 部署 Gradio UI4. 更多示例0. 引言 Browser-Use 是一个强大的工具,旨在让 AI Agent 能够控制浏览器,从而实现各种自动化任务。它简化了 AI 与浏览器的交互,让开发者能够轻松构建能够执行网页操作的智能应用。本…...
笔记:代码随想录算法训练营day59:110.字符串接龙 、105.有向图的完全可达性、106.岛屿的周长
学习资料:代码随想录 110. 字符串接龙 卡码网题目链接(ACM模式) 还是有些许复杂,要把字符串从begin开始遍历,然后把每一个字母都换一下,看能否在字典里找到,如果能找到就入队列并记录&#x…...
电力和冷却管理:如何让数据中心“高效降温”同时节能增效
电力和冷却管理:如何让数据中心“高效降温”同时节能增效 数据中心作为现代信息技术基础设施的核心,承担着处理、存储和传输海量数据的重任。然而,这些庞大的服务器和存储设备在高速运转时,不仅需要大量电力供应,还产生了大量热量。如何平衡电力消耗与有效冷却,成为了数…...
Vite管理的Vue3项目中monaco editer的使用以及组件封装
文章目录 背景环境说明安装流程以及组件封装引入依赖封装组件 外部使用实现效果 v-model实现原理 背景 做oj系统的时候,需要使用代码编辑器,决定使用Monaco Editor,但是因为自身能力问题,读不懂官网文档,最终结合ai和网友的帖子成功引入&…...
查找重复代码[A卷-hw_od]
题目描述 小明负责维护项目下的代码,需要查找出重复代码,用以支撑后续的代码优化,请你帮助小明找出重复的代码。 重复代码查找方法:以字符串形式给定两行代码(字符串长度 1 < length < 100,由英文字…...
HAl库开发中断方式接收Can报文的详细流程
下面给出一个基于 HAL 库的中断方式接收 CAN 报文的详细流程说明,描述每一步的硬件配置、软件调用和中断处理机制,而不涉及具体代码细节,只讲解整体原理和步骤: 在使用 HAL 库时,不需要手动清除中断标志位。原因如下&…...
[笔记] TinyWebServer编译及demo运行过程
文章目录 前言环境搭建ubuntumysql 8.0c/c开启root用户TinyWebServer 搭建及编译过程运行结果常见问题./threadpool/../CGImysql/sql_connection_pool.h:6:10: fatal error: mysql/mysql.h: No such file or directory./server运行后直接退出了 前言 哎 也就帮帮新手看看问题 …...
基于springboot的电影院管理系统(源码+lw+部署文档+讲解),源码可白嫖!
摘要 互联网技术的成熟和普及,势必会给人们的生活方式带来不同程度的改变。越来越多的经营模式中都少不了线上运营,互联网正强力推动着社会和经济发展。国人对民族文化的自信和不同文化的包容,再加上电影行业的发展,如此繁荣吸引…...
基于Redis分布锁+事务补偿解决数据不一致性问题
基于Redis的分布式设备库存服务设计与实现 概述 本文介绍一个基于Redis实现的分布式设备库存服务方案,通过分布式锁、重试机制和事务补偿等关键技术,保证在并发场景下库存操作的原子性和一致性。该方案适用于物联网设备管理、分布式资源调度等场景。 …...
虚拟电商-延迟任务系统的微服务改造(二)注册中心和Feign调用
一、微服务注册中心Consul 编写完延迟任务系统的web层接口,也就是说可以基于http协议来访问延迟系统,接下来要将延迟任务改造成一个服务。首要考虑的问题就是服务的注册与发现,服务的注册与发现都离不开服务的注册中心,本项目选取…...
数智读书笔记系列022《算力网络-云网融合2.0时代的网络架构与关键技术》读书笔记
一、书籍核心价值与定位 1.1 书籍概述:中国联通研究院的权威之作 《算力网络 —— 云网融合 2.0 时代的网络架构与关键技术》由中国联通研究院算力网络攻关团队精心撰写,是业界首部系统性探讨云网融合 2.0 与算力网络的专著。在云网融合从 1.0 迈向 2.0 的关键节点,本书的…...
人工智能在智能交通中的应用:以L4级无人电动物流拖车为例
一、引言 人工智能(AI)技术的飞速发展正在深刻改变各个行业,其中智能交通领域尤为显著。从自动驾驶汽车到智能交通管理系统,AI的应用不仅提高了交通效率,还增强了安全性。本文将重点探讨L4级无人电动物流拖车技术及其在…...
【愚公系列】《高效使用DeepSeek》024-儿童教育
🌟【技术大咖愚公搬代码:全栈专家的成长之路,你关注的宝藏博主在这里!】🌟 📣开发者圈持续输出高质量干货的"愚公精神"践行者——全网百万开发者都在追更的顶级技术博主! 👉 江湖人称"愚公搬代码",用七年如一日的精神深耕技术领域,以"…...
第十六届蓝桥杯康复训练--6
题目链接:790. 数的三次方根 - AcWing题库 思路:二分,注意正负号和小数判断退出的方法(虽然正负无所谓) 代码: #include<bits/stdc.h> using namespace std;#define exs 0.00000018812716007232667…...
【QA】单件模式在Qt中有哪些应用?
单例设计模式确保一个类仅有一个实例,并提供一个全局访问点来获取该实例。在 Qt 框架中,有不少类的设计采用了单例模式,以下为你详细介绍并给出相应代码示例。 1. QApplication QApplication 是 Qt GUI 应用程序的核心类,每个 Q…...
logisim安装以及可能出现的问题
阅读提示:我这篇文章更偏向于安装出现问题的解决方案 目录 一、安装步骤 二、安装问题 1、出错的问题 2、出错的原因与解决方法 一、安装步骤 1、下载logisim 官方网站:https://sourceforge.net/projects/circuit/ 下载适用于你操作系统的版本&…...
Servlet、HttpServletRequest、HttpServletResponse、静态与动态网页、jsp、重定向与转发
DAY15.2 Java核心基础 JavaWeb 要想通过浏览器或者客户端来访问java程序,必须通过Servlet来处理 没有Servlet,java是无法处理web请求的 Web交互: 接收请求HttpServletRequest:可以获取到请求的信息,比如uri&#…...
2300年直线公理使数学一直存在尖锐自相矛盾
2300年直线公理使数学一直存在尖锐自相矛盾 黄小宁 复平面z各点z的对应点2z的全体是2z平面。z面拉伸(平移)变换为2z面(z2面)就使x轴⊂z面沿本身拉伸(平移)变换为u2x轴(ux2轴)。R可…...
hackmyvm-Icecream
arp-scan -l nmap -sS -v 192.168.222.106 enum4linux 192.168.222.106 445端口 smbmap -H 192.168.222.106 icecream为只读模式 smbclient \\192.168.222.106\icecream 反弹shell(上传put php-reverse-shell.php) 开启监听 nc -lnvp 1234 拿到webshell cat /etc/passwd 9000端…...
Apache Tomcat漏洞公开发布仅30小时后即遭利用
近日,Apache Tomcat曝出一项安全漏洞,在公开发布概念验证(PoC)仅30小时后,该漏洞即遭到攻击者利用。这一漏洞编号为CVE-2025-24813,主要影响以下版本: 1. Apache Tomcat 11.0.0-M1 至 11.0.2 …...
告别低效人工统计!自动计算计划进度
实时监控任务进度一直是项目管理中的一项巨大挑战。 人工统计方式不仅耗时耗力,而且往往由于信息传递的延迟和人为误差,导致无法实时获得准确的项目进展信息。 这种不准确性可能掩盖潜在的风险点,从而影响项目的整体进度和成果。 Ganttable …...
AI比人脑更强,因为被植入思维模型【16】反脆弱
毛选中就有言,不经历困难,我们就不会掌握战胜困难的方法。 这个世界纷繁复杂,不是强者总是运气好,而是他们能够失败后快速复原,不断找到战胜困难的方法。 定义 马斯洛需求层次模型是一种将人类需求从低到高按层次进…...
L2TP实验
放开安全策略机制,FW1不配IP [FW1]firewall zone trust [FW1-zone-trust]add interface GigabitEthernet 1/0/0 [FW1]security-policy [FW1-policy-security]default action permit FW2 和FW3 [FW2]interface g1/0/1 [FW2-GigabitEthernet1/0/1]ip address 2…...
【数据预测】基于遗传算法GA的LSTM光伏功率预测 GA-LSTM光伏功率预测【Matlab代码#91】
文章目录 【可更换其他算法,获取资源请见文章第6节:资源获取】1. 遗传算法GA2. 长短期记忆网络LSTM3. 基于GA-LSTM的光伏功率预测4. 部分代码展示5. 运行结果展示6. 资源获取 【可更换其他算法,获取资源请见文章第6节:资源获取】 …...
