红黑树简单模拟实现
- 定义
- 成员变量
- 旋转
- insert
- 以234树的角度来待插入操作
- 具体代码
- 完整代码
我们前面实现了 二叉搜索树和 AVL树。
其中AVL树是二叉搜索树的改进,但是有些人觉得二叉树搜索的插入调整太频繁了,或者说平衡条件过于苛刻。
于是人们放松了左右子树高度差的限制,只需要确保没有一条路径会比其他路劲长出两倍,这就是所谓的红黑树。
定义
红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。
红黑树满足的性质:
- 每个结点不是红色就是黑色
- 根节点是黑色的
- 如果一个节点是红色的,则它的两个孩子结点是黑色的
- 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点
- 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)
满足这上面五个条件就确保了最长路径不会超过最短路径两倍。接下来我解释一下这是为什么。
首先看到34两点。满足了这两点之后,我们的理论最短路径就是全黑结点。
那最长路径呢?
因为要满足第四条,所以只能在全黑结点路径上插入红色结点来增长路径,又因为红色结点不能连续,因此最多插入等量的红色结点。这就确保了最长路径不会超过最短路径的两倍。
那为什么根节点要是黑色结点呢?
假如根节点是红色结点,那么他只能插入黑色结点,这就导致了左右子树的黑色结点数量不同。因此根节点只能是黑色的。
成员变量
enum Color
{RED,BLACK
};template<class K,class V>
struct RBTreeNode
{RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;pair<K, V> _kv;Color _col;RBTreeNode(const pair<K, V>& kv):_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _col(RED){}
};
template<class K, class V>
class RBTree
{typedef RBTreeNode<K,V> Node;
public:private:Node* _root = nullptr;
};
旋转
在前文AVL树中我们实现了左单旋和右单旋,那么这两种旋转能不能合并成一个函数呢?
注意到左单旋:
右单旋:
左单旋是将parent右子subR的左子树subRL接到parent的右边。
右单旋是将parent左子subL的右子树subLR接到parent的左边。
如此对称的行为理应可以合并:
void Rotate(Node* child)
{Node* parent = child->_parent;Node** childson[] = { &(child->_right),&(child->_left) };Node** parentson[] = { &(parent->_left),&(parent->_right) };bool IsRight = (child == parent->_right);*(parentson[IsRight]) = *(childson[IsRight]);if (*(childson[IsRight]))(*(childson[IsRight]))->_parent = parent;*(childson[IsRight]) = parent;child->_parent = parent->_parent;parent->_parent = child;if (parent == _root){_root = child;child->_parent = nullptr;}else {//Node* grandparent = parent->_parent; 千错万错Node* grandparent = child->_parent;if (parent == grandparent->_left) grandparent->_left = child;else grandparent->_right = child;}}
这时候如果要左单旋,就传入旋转点的右孩子,如果要右单旋就传入旋转点的左孩子。
insert
对于插入一个结点,那么是默认他为红色还是黑色呢?
如果默认是黑色就会违反所有路径黑色结点数相同这一个规定,如果默认是红色就有可能违反红色结点不连续这一规定。
显然插入红色结点的冲突比较好处理,因此我们默认插入的结点为红色结点。那么就分三种情况:
- 插入的结点是根节点,那只需要让根节点变黑即可
- 插入的结点的父结点是黑色结点,那么不违反规则,不做处理
- 插入的结点的父结点是红色结点,需要处理。
对于第三种情况,又要分两种情况进行讨论:
- 叔叔结点存在且为红色:
如上,10结点为插入结点。
首先是很容易想到的,为了维持到10结点这一路径的黑色结点个数不变,我们需要将20结点变黑同时将30结点变红,如果仅仅是将20结点变黑,那么到10结点这一路径的黑色结点数就会+1,违反规定4.
那么在30结点变红之后,到40结点这一路径的黑色节点数是不是就少了一个?所以我们要将40结点变黑。如下处理:
那么30结点有没有可能和他的父结点冲突呢?答案是有可能的,所以我们还需要将30结点看作插入结点继续循环处理。
- 那么再看到另一种情况,也就是叔叔结点不存在或者为黑色
实际上这种情况又详细分为四种,我们先看第一种插入结点为左子结点,叔叔结点为右子结点或者不存在:
由图可知,先将父亲变黑,爷爷变红然后进行右单旋调整
第二种插入结点为右子结点,叔叔结点为左子结点或者不存在,根据对称性可知,先将父亲变黑,爷爷变红然后进行左单旋调整
第三种插入结点为右子结点,叔叔结点为右子结点或者不存在
可以看到对父亲结点进行左单旋就转换成了第一种情况,这时按第一种情况除了
第四种插入结点为左子结点,叔叔结点为左子结点或者不存在
以234树的角度来待插入操作
我们可以将红黑树看作一棵二三四树,其中黑色结点为2结点,黑色结点加红色结点为3结点,黑色结点和两个红色结点为4结点。
例如:
这时我们插入一个16:
这就对应了父结点时黑色结点,不用处理
如果插入一个3:
这时候对应叔叔结点存在且为红。
那么5结点上溢,叔父变黑,爷爷变红,继续向上处理。
如果插入一个17:
本质上就是父亲做了爷爷,又因为父亲是右子结点,实际上就是进行左单旋。对应插入结点为右子结点,叔叔结点为左子结点或者不存在。
我们再看到如果插入25.5:
实际上是儿子做了爷爷,首先儿子要当爹,又因为儿子是左子结点,因此右单旋。然后儿子就是爷爷的右子结点了,再左单旋就成了爷爷。最后变色处理。
对应插入结点为左子结点,叔叔结点为左子结点或者不存在。
其余情况也是类似讨论,这里不多做赘述。
具体代码
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){//是否一定有爷爷?肯定的,因为父亲结点是红色,因此不是根节点。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;//未必有parentparent = cur->_parent;}else//叔叔不存在或者为黑{if (cur == parent->_left){//RotateR(grandfather);Rotate(grandfather->_left);parent->_col = BLACK;grandfather->_col = RED;}else{//RotateL(parent);//RotateR(grandfather);Rotate(parent->_right);Rotate(grandfather->_left);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;//未必有parentparent = cur->_parent;}else//叔叔不存在或为黑{if (cur == parent->_right){//RotateL(grandfather);Rotate(grandfather->_right);parent->_col = BLACK;grandfather->_col = RED;}else{RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}//处理边界情况_root->_col = BLACK;return true;
}
完整代码
完整代码包括InOrder和IsBalance
enum Color
{RED,BLACK
};template<class K,class V>
struct RBTreeNode
{RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;pair<K, V> _kv;Color _col;RBTreeNode(const pair<K, V>& kv):_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _col(RED){}
};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);cur->_col = RED; // 新增节点给红色if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;//父亲颜色是黑色结束while (parent&&parent->_col == RED){//是否一定有爷爷?肯定的,因为父亲结点是红色,因此不是根节点。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;//未必有parentparent = cur->_parent;}else//叔叔不存在或者为黑{if (cur == parent->_left){//RotateR(grandfather);Rotate(grandfather->_left);parent->_col = BLACK;grandfather->_col = RED;}else{//RotateL(parent);//RotateR(grandfather);Rotate(parent->_right);Rotate(grandfather->_left);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;//未必有parentparent = cur->_parent;}else//叔叔不存在或为黑{if (cur == parent->_right){//RotateL(grandfather);Rotate(grandfather->_right);parent->_col = BLACK;grandfather->_col = RED;}else{RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}//处理边界情况_root->_col = BLACK;return true;}void Rotate(Node* child){Node* parent = child->_parent;Node** childson[] = { &(child->_right),&(child->_left) };Node** parentson[] = { &(parent->_left),&(parent->_right) };bool IsRight = (child == parent->_right);*(parentson[IsRight]) = *(childson[IsRight]);if (*(childson[IsRight]))(*(childson[IsRight]))->_parent = parent;*(childson[IsRight]) = parent;child->_parent = parent->_parent;parent->_parent = child;if (parent == _root){_root = child;child->_parent = nullptr;}else {//Node* grandparent = parent->_parent; 千错万错Node* grandparent = child->_parent;if (parent == grandparent->_left) grandparent->_left = child;else grandparent->_right = child;}}//void RotateR(Node* parent)//{// Node* subL = parent->_left;// Node* subLR = subL->_right;// parent->_left = subLR;// if (subLR)// subLR->_parent = parent;// subL->_right = parent;// Node* ppNode = parent->_parent;// parent->_parent = subL;// if (parent == _root)// {// _root = subL;// _root->_parent = nullptr;// }// else// {// if (ppNode->_left == parent)// {// ppNode->_left = subL;// }// else// {// ppNode->_right = subL;// }// subL->_parent = ppNode;// }//}//void RotateL(Node* parent)//{// Node* subR = parent->_right;// Node* subRL = subR->_left;// parent->_right = subRL;// if (subRL)// subRL->_parent = parent;// subR->_left = parent;// Node* ppNode = parent->_parent;// parent->_parent = subR;// if (parent == _root)// {// _root = subR;// _root->_parent = nullptr;// }// else// {// if (ppNode->_right == parent)// {// ppNode->_right = subR;// }// else// {// ppNode->_left = subR;// }// subR->_parent = ppNode;// }//}bool IsBalance(){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);}void InOrder(){_InOrder(_root);cout << endl;}private:bool Check(Node* root,int blackNum,const int refNum){if (root == nullptr){if (blackNum != refNum){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);}void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_kv.first << ":" << root->_kv.second << endl;_InOrder(root->_right);}Node* _root = nullptr;//size_t _size;
};
相关文章:

红黑树简单模拟实现
定义成员变量旋转insert以234树的角度来待插入操作具体代码 完整代码 我们前面实现了 二叉搜索树和 AVL树。 其中AVL树是二叉搜索树的改进,但是有些人觉得二叉树搜索的插入调整太频繁了,或者说平衡条件过于苛刻。 于是人们放松了左右子树高度差的限制&…...

豪越科技:消防应急装备智能仓储管理新变革
在消防救援工作中,消防装备无疑是消防员们与火灾等灾害顽强对抗的关键“武器”。然而,传统的消防装备管理模式长期以来饱受诸多痛点的困扰,严重影响着消防工作的高效开展和救援效果。 在过去,装备丢失的情况时有发生。由于缺乏有效…...

如何设计Agent的记忆系统
最近看了一张画Agent记忆分类的图 我觉得分类分的还可以,但是太浅了,于是就着它的逻辑,仔细得写了一下在不同的记忆层,该如何设计和选型 先从流程,作用,实力和持续时间的这4个维度来解释一下这几种记忆&am…...

毕业论文格式(Word)
目录 Word目录怎么自动生成?快速生成试试这3个方法! - 知乎https://zhuanlan.zhihu.com/p/692056836目录生成需要先设置标题样式,这个不仅是目录生成需要,和后续的图表也有关系。 最好不要自己创建新的样式,而是在现有…...

学习STC51单片机14(芯片为STC89C52RC)
接下来我们进入学会了HC—SR04 还有舵机那么现在我们将他们融合在一起,用超声波来引导舵机的转动 我们这个最后的成果是做一个智能垃圾桶 成品是这样的,是不是可有意思了 成品视频 现在我们将舵机的代码和超声波测距模块的代码整合到一起,实…...

基于CodeBuddy实现本地网速的实时浏览小工具
本文所使用的 CodeBuddy 免费下载链接:腾讯云代码助手 CodeBuddy - AI 时代的智能编程伙伴 前言 在数字化浪潮席卷全球的今天,网络已成为人们生活和工作中不可或缺的基础设施。无论是在线办公、学习、娱乐,还是进行大数据传输和云计算&…...

stable diffusion论文解读
High-Resolution Image Synthesis with Latent Diffusion Models 论文背景 LDM是Stable Diffusion模型的奠基性论文 于2022年6月在CVPR上发表 传统生成模型具有局限性: 扩散模型(DM)通过逐步去噪生成图像,质量优于GAN&#x…...

计算机网络(3)——传输层
1.概述 1.1 传输层的服务和协议 (1)传输层为允许在不同主机(Host)上的进程提供了一种逻辑通信机制 (2)端系统(如手机、电脑)运行传输层协议 发送方:将来自应用层的消息进行封装并向下提交给 网络层接收方:将接收到的Segment进行组装并向上提交给应用层 …...

LangChain构建RAG的对话应用
目录 Langchain是什么? LangSmith是什么? 编辑 使用Python构建并使用AI大模型 数据解析器 提示模版 部署 记忆功能 Chat History -- 记忆 代码执行流程: 流式输出 构建向量数据库和检索器 检索器 代码执行流程 LLM使用检索器…...

目标检测DN-DETR(2022)详细解读
文章目录 gt labels 和gt boxes加噪query的构造attention maskIS(InStability)指标 在DAB-Detr的基础上,进一步分析了Detr收敛速度慢的原因:二分图匹配的不稳定性(也就是说它的目标在频繁地切换,特别是在训…...

嵌入式培训之系统编程(四)进程
一、进程的基本概念 (一)定义 进程是一个程序执行的过程(也可以说是正在运行的程序),会去分配内存资 源,cpu的调度,它是并发的 (二)PCB块 1、PCB是一个结构体&#x…...

天文数据处理:基于CUDA的射电望远镜图像实时去噪算法(开源FAST望远镜数据处理代码解析)
一、射电天文数据处理的挑战与CUDA加速的必要性 作为全球最大的单口径射电望远镜,中国天眼(FAST)每秒产生38GB原始观测数据,经预处理后生成数千万张图像。这些数据中蕴含的脉冲星、中性氢等天体信号常被高斯白噪声、射频干扰&…...

VS编码访问Mysql数据库
安装 MySQL Connector/C 的开发包 libmysqlcppconn-dev是 MySQL Connector/C 的开发包,它的主要用途是让 C 开发者能够方便地在应用程序中与 MySQL 数据库进行交互。它提供了以下功能: 数据库连接:通过标准的 C 接口连接到 MySQL 数据库。S…...

一周学会Pandas2 Python数据处理与分析-Pandas2数据合并与对比-pd.merge():数据库风格合并
锋哥原创的Pandas2 Python数据处理与分析 视频教程: 2025版 Pandas2 Python数据处理与分析 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili pd.merge():数据库风格合并 **核心功能**:基于列值(类似 SQL JOIN)合…...
leetcode 862. 和至少为 K 的最短子数组
这段代码使用了前缀和单调队列的组合策略来高效解决"和至少为K的最短子数组"问题。我将从问题定义、核心思路到代码实现逐步拆解: 问题定义 给定数组 nums 和整数 k,找到和 ≥k 的最短非空子数组,返回其长度。 示例:n…...

CodeBuddy 实现图片转素描手绘工具
本文所使用的 CodeBuddy 免费下载链接:腾讯云代码助手 CodeBuddy - AI 时代的智能编程伙伴 前言 最近在社交媒体上,各种素描风格的图片火得一塌糊涂,身边不少朋友都在分享自己的 “素描照”,看着那些黑白线条勾勒出的独特韵味&a…...

3.8.2 利用RDD计算总分与平均分
在本次实战中,我们利用Spark的RDD完成了成绩文件的总分与平均分计算任务。首先,准备了包含学生成绩的文件并上传至HDFS。接着,通过交互式方式逐步实现了成绩的读取、解析、总分计算与平均分计算,并最终输出结果。此外,…...

29-FreeRTOS事件标志组
一、概述 事件是一种实现任务间通信的机制,主要用于实现多任务间的同步,但事件通信只能是事件类型的通信,无数据传输。与信号量不同的是,它可以实现一对多,多对多的同步。 即一个任务可以等待多个事件的发生࿱…...
天地图实景三维数据分享(江苏)
1、天地图介绍 “天地图”(MAPWORLD)是国家地理信息公共服务平台 ,2011年正式上线 ,是自然资源部门向社会提供各类在线地理信息公共服务、推动地理信息数据开放共享的政府网站 ;是中国区域内基础地理信息数据资源最全…...
Jenkins的Pipline中有哪些区块,以及其它知识点整理
目录 ■模板 ■Jenkins的Pipline中有哪些区块 1. pipeline(顶层区块) 2. agent(执行节点) 3. stages(阶段集合) 4. stage(单个阶段) 5. steps(具体步骤࿰…...

「EMD/EEMD/VMD 信号分解方法 ——ECG信号处理-第十四课」2025年5月23日
一、引言 上一节,我们介绍了希尔伯特黄变换(HHT)及其经验模态分解(EMD)的相关内容,这一节,我们继续拓展EMD分解技术,补充介绍集合经验模态分解(Ensemble Empirical Mode …...

二叉树层序遍历6
INT_MIN的用法: INT_MIN是C/C 中的一个宏常量 ,在 <limits.h> (C 中也可使用 <climits> )头文件中定义,代表 int 类型能表示的最小整数值 。其用法主要体现在以下方面: 1.初始化变量 …...

【论文精读】2023 AAAI--FastRealVSR现实世界视频超分辨率(RealWorld VSR)
文章目录 一、摘要二、Method2.1 现象(问题)--对应文中隐状态的分析(Analysis of Hidden State)2.2 怎么解决 --对应文中Framework2.2.1 整体流程:2.2.2 HSA模块怎么工作?2.2.2.1 隐藏状态池2.2.2.2 选择性…...

IPython 常用魔法命令
文章目录 IPython 魔法命令(Magic Commands)一、系统与文件操作1. %ls2. %cd和%pwd3. %%writefile4. %run 二、性能分析与计时1. %timeit2. %prun3. %%timeit 三、代码处理与交互1. %load2. %edit3. %store 四、调试与诊断2. …...
数据同步自动化——如何用Python打造高效工具?
友友们好! 我是Echo_Wish,我的的新专栏《Python进阶》以及《Python!实战!》正式启动啦!这是专为那些渴望提升Python技能的朋友们量身打造的专栏,无论你是已经有一定基础的开发者,还是希望深入挖掘Python潜力的爱好者,这里都将是你不可错过的宝藏。 在这个专栏中,你将会…...
开源与闭源之争:AI时代的创新博弈与未来抉择
在人工智能技术狂飙突进的今天,开源与闭源之争已不再局限于技术圈的讨论,而是演变为一场关乎技术伦理、商业格局乃至人类文明走向的深度博弈。当Meta的Llama 3开源模型下载量突破百万,当OpenAI的GPT-5继续加固技术壁垒,这场没有硝…...
flutter dart class语法说明、示例
🔹 Dart 中的 class 基本语法 class ClassName {// 属性(字段)数据类型 属性名;// 构造函数ClassName(this.属性名);// 方法返回类型 方法名() {// 方法体} }✅ 示例:创建一个简单的 Person 类 class Person {// 属性String name;…...

Java虚拟机 - 程序计数器和虚拟机栈
运行时数据结构 Java运行时数据区程序计数器为什么需要程序计数器执行流程虚拟机栈虚拟机栈作用虚拟机栈核心结构运行机制 Java运行时数据区 首先介绍Java运行时数据之前,我们要了解,对于计算机来说,内存是非常重要的资源,因为内…...
SpringMVC04所有注解按照使用位置划分| 按照使用层级划分(业务层、视图层、控制层)
目录 一、所有注解按照使用位置划分(类、方法、参数) 1. 类级别注解 2. 方法级别注解 3. 参数级别注解 4. 字段/返回值注解 二、按照使用层级划分(业务层、视图层、控制层) 1、控制层(Controller Layer&#x…...

新能源汽车产业链图谱分析
1. 产业定义 新能源汽车是指采用非常规的车用燃料作为动力来源,综合车辆的动力控制和驱动方面的先进技术,形成的具有新技术、新结构、技术原理先进的汽车。 新能源车包括四大类型:混合动力电动汽车(HEV)、纯电动汽车…...