[C++进阶数据结构]红黑树(半成品)

 我们讲完了AVL树,它追求绝对平衡,从而导致插入和删除性能较差。今天我们来讲讲,红黑树,它是另一种平衡二叉搜索树,它追求相对平衡,使得增删查改的性能都极佳,时间复杂度皆为O(log2N)。

一、红黑树的概念

1.红黑树的概念

红黑树,顾名思义,其节点有红和黑两种颜色。

之所以新增结点颜色的标记,是因为通过结点着色方式的限制,能够让红黑树的最长路径不超过最短路径的两倍,以保证相对平衡。

红黑树满足五条性质:

  1. 所有结点非黑即红
  2. 根结点为黑色
  3. NIL结点为黑色
  4. 红色结点的子结点必为黑色
  5. 任意结点到其叶子NIL结点的所有路径,都包含相同的黑色结点

在红黑树中,NIL节点(也称为空节点)是叶子节点的一种特殊表示。它们不是实际存储数据的节点,而是树结构中的占位符,用于定义树的边界。所有的红黑树都以NIL节点为叶子节点,这些NIL节点在视觉上通常不被画出来

2.红黑树的性质

上面讲的不够详细这里我接着补充

红黑树的结点的旋转是和颜色息息相关

首先我们先来看这个红黑树:

这红黑树有:

11条路径(注意是11条因为空节点也包括在内)

如果没懂,我们来看下面这棵:

是不是有人认为这是红黑树了?

实际上它不是红黑树。

如果我们不看每个空结点的话,它看上去有四条路径,且每条路径都只有两个黑色结点,看上去好像是红黑树。但是实际上要包括空结点,且空结点是黑色的。所以一共有九条路径,最左边的一条路径只有两个黑色结点,其他路径都有三个黑色结点。

每个结点的每条路径上的黑色结点个数相同,但是由于每个结点的祖先的路径是唯一确定的,所以我们只需要判断从根节点到每个空结点上的路径中黑色结点个数是否相同即可。

但是我们知道最长路径不超过最长路径的两倍,这个性质该怎么解决呢?

其实我们只要保证每个结点的每条路径上的黑色结点个数相同,该性质就成立了!

3.红黑树和AVL树的对比

既然有了AVL树,那么为什么要有红黑树呢?其实是因为AVL树太严格了。它要控制平衡就需要付出代价。而红黑树要求并不严格,综合来看,红黑树的综合性能其实更优

AVL树红黑树
高度对比高度差不超过一最长路径不超过最短路径的两倍
插入1000个值logN---->102*logN---->20
插入10亿个值logN---->302*logN---->60

我们可以看到,虽然他们的高度有点差异,但是他们的效率都是同一个量级的,而且cpu的速度是非常快的,这点效率对于cpu几乎没有任何区别

性能是同一量级的,但是AVL树控制严格平衡是需要付出代价的,插入和删除需要大量的旋转。

二、红黑树的模拟实现

1. 结点

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

细节:

  1. 使用三叉链,增加了指向parent的指针
  2. 使用KV模型,数据存储键值对pair
  3. 结点储存颜色,同时颜色使用枚举
  4. 结点的颜色初始化为红色

说明:为什么结点的颜色初始化为红色呢?因为插入新节点时(不为根部),如果插入黑色,就会直接破坏性质5,导致每条路径黑结点数目不同;而如果插入红色,有可能不会破坏性质4,所以结点初始化为红色更优。

2. 成员变量       

template<class K, class V>
class RBTree
{
protected:typedef RBTreeNode<K, V> Node;
public:
protected:Node* _root = nullptr;
};

3.插入

1) 红黑树的结点定义

红黑树有红色结点和黑色结点两种颜色。所以不妨我们使用一个枚举类型来进行表示。红黑树里面我们需要有一个pair类型来进行存储key和value类型的数据。然后我们定义三个指针,分别指向父亲,左孩子,右孩子。最后定义其的颜色。在构造函数中,我们将其的颜色默认设置为红色。

设置为红色是比较有讲究的。那是因为我们大多数场景下都是去创建一个新节点去插入的。如果我们插入的这个新结点是黑色的话,那么造成的后果就是违反了规则4,即每条路径上的黑色结点个数相同,显然这种情况要进行补救的话是十分麻烦的。还不如去插入红色结点,如果插入的是红色结点的话,仅仅有可能会违反规则3,也就是红色结点的孩子必须是黑色结点。这一点的话,我们还有的补救,因为仅仅影响本条路径。

enum Color
{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;Color _color;RBTreeNode(const pair<K, V>& kv):_kv(kv),_left(nullptr),_right(nullptr),_parent(nullptr),_color(RED) // 插入红色结点,违反规则3,只影响一条路径,甚至可能不影响。如果插入黑色结点,违反规则4,影响所有路径{}
};
2)父亲的颜色

因为我们要插入的结点是红色的,那么在我们插入的位置,它的父亲要么是红色的要么是黑色的。如果是黑色的,如下:

我们可以看到,似乎并未影响整棵树的结构,不违反任何一条规则。最短路径仍然不超过最长路径的两倍。每条路径上的黑色结点个数仍然相等。所以如果插入一个新节点以后,如果此处它的父亲是黑色的,完美,不需要做出任何修改即可。如果父亲甚至都不存在,那么这个结点就是这颗树了。我们只需要将这个结点变为黑色即可。如果父亲是红色的话,那么就比较麻烦了。

如下图所示,此时我们需要对这颗树进行处理了。

3. 叔叔的颜色为红色

如果我们插入了一个结点以后,此时,父亲结点为红色,且有父亲的兄弟,即叔叔,且叔叔的颜色是红色。

即如下的情况,uncle存在且为红

这样的话,我们可以将parent和uncle都变黑,然后让grandfather变红,即如下的情形

这样的话,在黑色结点数量不变的条件下,使得连续红色结点的问题暂时解决了。现在原本cur为红色的问题转换为了grandfather为红色的问题。

此时的话,如果这个grandfather如果没有父亲了,那么根据规则1,我们只需要让他变为黑色即可。此时仅仅只是所有的路径多了一个黑色结点。如果grandfather有父亲,那么我们只需要让grandfather变为cur,继续向上处理即可

在这里继续向上处理的时候又分为以下几种情况:

grandfather没有父亲,那么直接让grandfather变黑即可
grandfather有父亲,且父亲为黑色,那么由于前面的树本身就是满足红黑树规则,这里改变了之后仍然满足红黑树规则,那么不处理即可
grandfather有父亲,且父亲为红色,这样的情况下,父亲为红色,就隐含了必然存在grandfather的grandfather,因为原来的树也要满足红黑树的规则,这样一来就是类似的问题继续往处理即可。

然后由于此时恰好uncle存在且为红,那么我们只需要按照前面的逻辑进行处理即可,即uncle和parent均变黑,然后grandfather变为红

然后又为了满足根节点为黑,所以grandfather变为黑即可

4. 叔叔不存在

如下图所示,当叔叔不存在的时候,我们插入了一个新节点以后,我们发现最长路径已经超过了最短路径的两倍了。这时候单纯的进行变色,已经无法解决问题了。

此时我们需要进行旋转

然后再变色了
对于旋转:判断规则与AVL基本是一致的,如果是直线那么就是单旋,我们只需要分析谁高就可以了。如果是折线就是双旋,我们还是分析哪边高就可以了。
如上图所示的情形就是右单旋就可以了

5. 叔叔存在且为黑

我们接着前面的图,我们继续插入一个新的结点


那么此时的uncle存在且为红,我们进行相应的处理后,变为如下的情况

这时候,uncle存在且为黑,那么此时的处理情形就和前面的uncle不存在是一样的,直接旋转加变色。这里的如何旋转和变色统一结论后面详细讨论

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

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

相关文章

CSS3 动画相关属性实例大全(三)(columns、filter、flex、flex-basis 、flex-grow、flex-shrink属性)

CSS3 动画相关属性实例大全&#xff08;三) &#xff08;columns、filter、flex、flex-basis 、flex-grow、flex-shrink属性&#xff09; 本文目录&#xff1a; 一、columns属性&#xff08;设置元素的列宽和列数&#xff09; 二、filter属性&#xff08;调整图像、背景和边…

Ribbon客户端负载均衡策略测试及其改进

文章目录 一、目的概述二、验证步骤1、源码下载2、导入IDE3、运行前修改配置4、策略说明5、修改策略 三、最终结论四、改进措施1. 思路分析2. 核心代码3. 测试页面 一、目的概述 为了验证Ribbon客户端负载均衡策略在负载节点失效的情况下&#xff0c;是否具有故障转移的功能&a…

【逆向基础】十七、PE文件格式(二)

一、简介 本篇章主要PE文件组成部分中使用的结构体&#xff1b;根据结构体的成员变量去了解各个字节的含义。&#xff08;ps:我们依旧以”cmd.exe“为例展开解析&#xff1b;) 二、DOS Header 1、结构体&#xff1a;IMAGE_DOS_HEADER IMAGE_DOS_HEADER结构体的背景是为了兼…

忘记7-zip文件7-zip文件,还可以解压zip文件吗?

文件压缩与解压已成为我们日常处理数据和存储信息的常规操作。7-Zip&#xff0c;作为一款开源且功能强大的文件压缩工具&#xff0c;凭借其高压缩率、支持多种格式以及免费使用的特点&#xff0c;赢得了广大用户的青睐。然而&#xff0c;出于保护文件内容安全的考虑&#xff0c…

基于NVIDIA NIM平台—生成属于自己的DIY食谱

目录 一、介绍NVIDIA NIM平台 二、生成DIY食谱Demo 三、小结 一、介绍NVIDIA NIM平台 NVIDIA NIM&#xff08;Nvidia Inference Microservices&#xff09;平台是NVIDIA推出的一个微服务套件&#xff0c;旨在加速生成式AI模型在云端、数据中心和工作站上的部署和使用。以下是…

怎么区分主谓宾I love you与主系表I am fine? 去掉宾语看句子完整性 主系表结构则侧重于描述主语的状态、特征或性质

主谓宾与主系表是英语句子结构中的两种基本类型&#xff0c;它们在关注点、动词分类以及句子完整性方面有所区别。具体分析如下&#xff1a; 关注点 主谓宾I love you&#xff1a;主谓宾结构主要关注动作和影响对象之间的关系[1]。这种结构强调的是动态和行为&#xff0c;通常描…

4K双模显示器7款评测报告

4K双模显示器7款评测报告 HKC G27H7Pro 4K双模显示器 ROG华硕 XG27UCG 4K双模显示器 雷神 ZU27F160L 4K双模显示器 泰坦军团 P275MV PLUS 4K双模显示器 外星人&#xff08;Alienware&#xff09;AW2725QF 4K双模显示器 SANC盛色 D73uPro 4K双模显示器 ANTGAMER蚂蚁电竞 …

MySql中表的约束

​ 本篇中将会介绍关于 MySql 数据库中的表的约束&#xff0c;关于表的约束其实约束的是表中的数据类型&#xff0c;因为有的数据类型很单一&#xff0c;需要我们添加一些额外的约束&#xff0c;才能更好的保证数据的合法性&#xff0c;从业务逻辑角度保证数据的正确性&#xf…

Notepad++通过自定义语言实现日志按照不同级别高亮

借助Notepad的自定义语言可以实现日志的按照不同级别的高亮&#xff1b; 参考&#xff1a; https://blog.csdn.net/commshare/article/details/131208656 在此基础上做了一点修改效果如下&#xff1a; xml文件&#xff1a; <NotepadPlus><UserLang name"Ansibl…

leetCode算法题爬楼梯递归写法

题目&#xff1a; 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 示例 1&#xff1a; 输入&#xff1a;n 2输出&#xff1a;2解释&#xff1a;有两种方法可以爬到楼顶。1. 1 阶 1 阶2. 2 阶 …

GPIO输入和输出

参考视频&#xff1a;2.1 [GPIO]4种输出模式_哔哩哔哩_bilibili 输出&#xff1a;通过写0或者写1&#xff0c;控制引脚输出低电压或高电压。 输入&#xff1a;通过读取引脚是0还是1&#xff0c;判断引脚输入的是高电压还是低电压。 输出 推挽开漏通用通用输出推挽通用输出开漏…

Asp.net Core MVC 动态路由

动态路由 asp.net core 3.0 就支持了 // 映射关系public class TranslationDatabase{private static Dictionary<string, Dictionary<string, string>> Translations new Dictionary<string, Dictionary<string, string>>{{"en", new Dictio…

yolo自动化项目实例解析(八)自建UI-键鼠录制回放

项目中关于键鼠的操作&#xff0c;不像我们之前自动化那样一步一步去定义的&#xff0c;而是用C写了一个记录键鼠的操作&#xff0c;通过回放的方法来实现的 一、通讯系统 1、创建websocket服务器 首先通过事件循环asyncio 和websockets&#xff0c;创建一个持久化的服务端进程…

通过页面添加国际化数据,实现vue的国际化

element ui 写在前面1. 原有的vue的国际化处理1.1 语言文件1.2 lang的index.js1.3 入口文件导入1.3 应用 2. 通过页面添加国际化数据2.1 做法2.2 lang的index.js文件修改2.3 需要注意的点 总结写在最后 写在前面 需求&#xff1a;在系统的国际化管理页面添加国际化数据&#x…

我想电脑批量管理 30 台苹果手机,怎么操作更简单方便呢?

在如今的数字化时代&#xff0c;手机已经成为了我们日常生活中不可或缺的一部分。无论是工作还是娱乐&#xff0c;我们都需要使用各种各样的应用软件来满足自己的需求。 而对于那些需要管理大量苹果手机设备的企业来说&#xff0c;如何高效地完成这些任务就成了一个重要问题。…

三款计算服务器配置→如何选择科学计算服务器?

科学计算在众多领域都扮演着关键角色&#xff0c;无论是基础科学研究还是实际工程应用&#xff0c;强大的计算能力都是不可或缺的。而选择一台合适的科学计算服务器&#xff0c;对于确保科研和工作的顺利进行至关重要。 首先&#xff0c;明确自身需求是重中之重。要仔细考虑计算…

六个方向比较分析:ChatGPT-o1-preview与 ChatGPT-4o在论文写作辅助上的差异

学境思源&#xff0c;一键生成论文初稿&#xff1a; AcademicIdeas - 学境思源AI论文写作 在学术研究和论文撰写的领域&#xff0c;人工智能助手正变得越来越重要。随着技术的不断进步&#xff0c;ChatGPT-o1-preview和ChatGPT-4o作为两个先进的语言模型&#xff0c;在辅助论文…

文件上传漏洞及安全

文件上传 文件上传安全指的是攻击者通过利用上传实现后门的写入连接后门进行权限控制的安全问题&#xff0c;对于如何确保这类安全问题&#xff0c;一般会从原生态功能中的文件内容&#xff0c;文件后缀&#xff0c;文件类型等方面判断&#xff0c;但是漏洞可能不仅在本身的代码…

C++学习路线(二十二)

构造函数 构造函数作用 在创建一个新的对象时&#xff0c;自动调用的函数&#xff0c;用来进行“初始化”工作:对这个对象内部的数据成员进行初始化。 构造函数特点 1.自动调用(在创建新对象时&#xff0c;自动调用) 2.构造函数的函数名&#xff0c;和类名相同 3.构造函数…

Pytorch学习--如何下载及使用Pytorch中自带数据集,如何把数据集和transforms联合在一起使用

一、标准数据集使用 pytorch官网–标准数据集 这里以CIFAR10数据集为例&#xff1a;CIFAR10 下载数据集 代码&#xff1a; import torchvision train_datatorchvision.datasets.CIFAR10(root"datasets",trainTrue,downloadTrue) test_datatorchvision.datasets.…