C++:红黑树

目录

一、关于红黑树

1、红黑树和AVL树区别

2、红黑树规则

二、红黑树

1、红黑树变色举例

2、红黑树具体情况分析

①、情况一:只变色

②、情况二:单旋 + 变色

③、情况三:双旋 + 变色

三、红黑树的模拟实现


一、关于红黑树

1、红黑树和AVL树区别

我们前面学过的AVL树是左右子树的高度差不超过1

红黑树则是最长路径不超过最短路径的2倍

由于AVL树是严格要求平衡的,而红黑树并不严格要求,是接近平衡的,所以在插入相同的数据时,AVL树旋转更多,而红黑树旋转更少


2、红黑树规则

首先红黑树也是一种二叉搜索树,但是结点增加了颜色属性,红色或黑色,同时红黑树确保了:最长路径不超过最短路径的2倍,具体红黑树性质如下:

①、每个结点不是红色就是黑色

②、根结点必须是黑色

③、如果一个结点是红色的,那么它的孩子必须是黑的(也就是说,红黑树中不会出现连续的红色结点)

④、对于每个结点,从该结点到后代叶节点的简单路径上,都包含相同数量的黑色结点(每条路径的黑结点个数相同)

⑤、每个叶子结点(NIL结点)都是黑色的(空结点是黑色的)


需要注意的是,为什么满足这几条规则就可以保证最长路径不超过最短路径的2倍:

因为我们可以想想,由于根结点是黑色,那么为了保证两个红结点不能连续出现,每个路径黑结点个数相同,那么最短路径最极限的情况就是只出现黑结点

而我们知道红结点的左右字树必须是黑结点,且每条路径的黑结点数量必须相同,因此想要增加结点只能增加红结点,所以最短路径假设有n个黑结点,那么在最长路径中,这n个黑结点每一个黑结点的下面,极限情况来说最多跟着一个红结点,如下图所示:

因此理想情况最长路径也就是多了与最短路径的黑结点数量相同的红结点,所以在最极限的情况下,最长路径也只是2n个结点,而最短路径是n个结点,因此可以保证最长路径不超过最短路径的2倍


二、红黑树

1、红黑树变色举例

先举个例子,现在我们想在红黑树中插入一个结点,那么就有两个选择,插入的结点是红色或黑色,接下来就要考虑到底是选红色还是黑色 

首先,如果选红色,可能会违反红黑树的规则3,因为插入结点的父结点也可能是红色的

如果选黑色,则一定会违反红黑树的规则4,因为在一个路径下插入黑色结点,会导致无法满足该路径与其他路径的黑色结点相同

所以对比来看,我们一定选择的插入结点是红色的,因为选红色,可能会违反规则,也可能不会违反,而选黑色一定会违反;其次,选红色如果违反,相比较而言,规则3比规则4好修正一些

下面举个只变色例子方便理解红黑树的插入:

图中的cur、par、un、grapar代表某一个结点

cur就是当前位置的结点

par就是parent,即cur的父结点

grapar就是grandparent,即parent的父结点

un就是uncle,即parent的兄弟结点

cur插入到par的左边,这时两个红结点相连了,而规则3表示不能两个红结点相连,由于我们已经选择了cur为红色,所以唯一的措施就是将par变黑,这时需要注意,如果par有兄弟结点,如上图的un,也需要一同变黑,随着un和par变黑,会让他们这两条路径多一个黑结点,不满足规则4,所以将他们的父结点grapar变红,这时满足规则4,第一次更改结束


下面是第二次更改:

将第一次的四个位置对应的结点做以改变,如上图,接着重复第一步的操作,cur和par都是红色,所以改par为黑,而par的兄弟结点un也改为黑,这两条路径都多了个黑结点,所以grapar变为红,第二次改变结束


第三次:

将对应位置改变完后,发现不满足规则2,根结点cur是红色的,又发现cur的parent为空,所以将cur变为黑色,从而满足规则2,第三次改变结束,整个红黑树的插入过程就结束了

当然也有变色+旋转的情况,下面会说到,上面就是为了方便说清楚红黑树是怎么变的


2、红黑树具体情况分析

图中的cur就是当前位置的结点

p就是parent,即cur的父结点

g就是grandparent,即parent的父结点

u就是uncle,即parent的兄弟结点

①、情况一:只变色

情况一是cur为红,p为红,g为黑,u存在为红

为了方便起见,上图中出现的cur、par、un、grapar这几个单词分别在图中用cur、p、u、g表示

具体情况太多,所以这里用抽象图做以说明,且抽象图可能是完整的树,也可能是子树

其中三角形a/b/c/d/e是子树,可能存在也可能不存在

场景1:a/b/c/d/e是空树,cur是新增结点

按照上面举例的红黑树变色样例的规则,变色处理

如果g不是根,把g当做cur继续往上处理

如果g是根,把g变为黑色,处理结束


场景2:cur不是新增,a/b/c/d/e子树不为空

可能是由下面的新增变化得来的

cur不是新增结点,而是由新增节点变色后得到情况1的场景

接着继续继续变色处理:

就和场景1一样,如果g不是根,把g当做cur继续往上处理

如果g是根,把g变为黑色,处理结束


②、情况二:单旋 + 变色

注意,下面的单旋双旋就不具体说怎么操作的,在AVL树部分的博客已经详细说明了

情况二是cur为红,p为红,g为黑,u不存在/u存在且为黑

场景1:a/b/c/d/e是空树,cur是新增结点,u不存在

这时u不存在,不能直接将p变黑,相当于凭空多了一个黑结点,情况1有u的时候,可以将p和u同时变黑,再将g变红,相当于黑结点不多不少,而u不存在的情况,直接将p变黑,如果将来是下图这种情况,就会有问题:

如上图,如果依然只进行变色处理会导致最左边路径的黑结点多于其他路径的黑结点

这时要进行右单旋 + 变色处理:

即完成处理


场景2:a/b/c/d/e不是空树,cur不是新增结点,u存在且为黑

这种场景下,d/e可以是空树或者一个红结点,我们当做空树处理

c可以是根是黑结点的子树,即下面四种的任意一种,我们选择第一种

上图变为:

这时第一步变色处理后,如图:

接着和场景1一样,g结点进行右单旋处理,且p变黑g变红

场景2处理结束


③、情况三:双旋 + 变色

情况三是cur为红,p为红,g为黑,u不存在/u存在且为黑

与情况二不同的是情况三cur、g、p三个结点是折线,而情况二是直线,模型图如下:

a/b/c/d/e是子树


场景1:a/b/c/d/e是空树,cur是新增结点,u不存在

p结点先左单旋:

这时图形就变为和情况二一样的形状了

这时再将结点g进行右单旋,并且g变为红,cur变为黑

这时变处理完成了


场景2:a/b/c/d/e不是空树,cur不是新增结点,u存在且为黑

这种场景下,d/e可以是空树或者一个红结点,我们当做空树处理

a可以是根是黑结点的子树,即下面四种的任意一种,我们选择第一种

这时上图变为:

这时第一步变色处理后,如图:

变色处理后,上半部分的样子就场景1的形状一样了

接着和上面场景1一样,结点p左单旋:

这时就和情况二一样,再将结点g右单旋,同时将g变为红,cur变为黑

至此这种场景也处理完成了


通过这三种情况,我们可以发现,uncle这个结点是至关重要的

uncle存在且为红色,则变色继续网上处理

uncle存在且为黑或是uncle不存在,则需要旋转+变色处理,具体单旋还是双旋看具体情况


三、红黑树的模拟实现

具体见代码,有详细注释

//颜色:红色或黑色,使用枚举
enum Color
{RED,BLACK
};//和AVL树构造差不多,多了颜色,少了平衡因子
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){}
};template<class K, class V>
struct RBTree
{typedef RBTreeNode<K, V> Node;
public:bool insert(const pair<K, V>& kv){//如果根结点为空,则用kv来new一个新结点//并将颜色给为黑色if (_root == nullptr){_root = new Node(kv);_root->_col = BLACK;return true;}//遍历,直到找到空结点Node* cur = _root;Node* parent = nullptr;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;}//链接插入结点的parentcur->_parent = parent;//循环判断是否处理结束while (parent && parent->_col == RED){Node* grandparent = parent->_parent;//断言检查grandparent颜色及是否存在assert(grandparent);assert(grandparent->_col == BLACK);//判断uncle结点在grandparent的左还是右if (parent == grandparent->_left){Node* uncle = grandparent->_right;//情况一	:u存在且为红,变色+继续往上处理if (uncle && uncle->_col == RED){//p和u变黑,g变红parent->_col = uncle->_col = BLACK;grandparent->_col = RED;//接着将g当做cur继续往上执行cur = grandparent;//找到cur的父结点parent = cur->_parent;}//情况二 + 情况三  else{//情况二:右单旋 + 变色处理//    g//  p   u  //cif (cur == parent->_left){RotateR(grandparent);parent->_col = BLACK;grandparent->_col = RED;}else{//情况三:左右双旋 + 变色处理//    g//  p   u  //   cRotateL(parent);RotateR(grandparent);cur->_col = BLACK;grandparent->_col = RED;}//情况二和情况三处理完后//不论g是否是根结点都不影响//所以不用往上执行,直接breakbreak;}}else//parent == grandparent->_right{Node* uncle = grandparent->_left;//情况一:u存在且为红,变色+继续往上处理if (uncle && uncle->_col == RED){//p和u变黑,g变红parent->_col = uncle->_col = BLACK;grandparent->_col = RED;//接着将g当做cur继续往上执行cur = grandparent;//找到cur的父结点parent = cur->_parent;}//情况二 + 情况三  else{//情况二:左单旋 + 变色处理//    g//  u   p  //        cif (cur == parent->_right){RotateL(grandparent);parent->_col = BLACK;grandparent->_col = RED;}else{//情况三:右左双旋 + 变色处理//    g//  u   p  //     cRotateR(parent);RotateL(grandparent);cur->_col = BLACK;grandparent->_col = RED;}//情况二和情况三处理完后//不论g是否是根结点都不影响//所以不用往上执行,直接breakbreak;}}}//将根结点的颜色保持黑色_root->_col = BLACK;return true;}//判断是否是红黑树bool IsRBTree(){if (_root == nullptr){return true;}if (_root->_col == RED){cout << "根结点为红色" << endl;return false;}//黑色结点数量的一条路径的值//用以比较和其他路径是否相同int mark = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK){++mark;}cur = cur->_left;}return PrevCheck(_root, 0, mark);}private://在检查是否是红黑树时//前序遍历检查每条路径的黑色节点数量是否相同bool PrevCheck(Node* root, int blackNum, int mark){//当走到空结点时if (root == nullptr){//如果不相等,return falseif (blackNum != mark){cout << "有条路径黑色结点数量不相同" << endl;return false;}//如果相等,return trueelse{return true;}}//是黑色就++blackNumif (root->_col == BLACK){++blackNum;}//判断是否有两个连续的红结点if (root->_col == RED && root->_parent->_col == RED){cout << "存在两个连续的红结点" << endl;return false;}//前序遍历return PrevCheck(root->_left, blackNum, mark)&& PrevCheck(root->_right, blackNum, mark);}//左单旋void RotateL(Node* parent){//parR是parent的右孩子//parRL是parR的左孩子Node* parR = parent->_right;Node* parRL = parR->_left;parent->_right = parRL;//链接_parent的关系//可能出现parR为空的情况if (parRL)parRL->_parent = parent;parR->_left = parent;//记录一下parent->_parent,为下面的第二种情况Node* parP = parent->_parent;//链接_parent的关系parent->_parent = parR;//两种情况,平衡因子是2的结点是否是整棵树的根结点//1、平衡因子为2的结点是整棵树的根结点if (_root == parent){_root = parR;parR->_parent = nullptr;}//2、平衡因子为2的结点是子树的根结点else{if (parP->_left == parent){parP->_left = parR;}else{parP->_right = parR;}parR->_parent = parP;}}//右单旋void RotateR(Node* parent){//定义parL和parLR//parL指parent的左子树//parLR指parL的右子树Node* parL = parent->_left;Node* parLR = parL->_right;parL->_right = parent;//记录parent的_parent,为下面的情况2做准备Node* parP = parent->_parent;parent->_parent = parL;parent->_left = parLR;if (parLR)parLR->_parent = parent;//两种情况,平衡因子是-2的结点是否是整棵树的根结点//1、平衡因子为-2的结点是整棵树的根结点if (_root == parent){_root = parL;parL->_parent = nullptr;}//2、平衡因子为-2的结点是子树的根结点else{if (parP->_left == parent){parP->_left = parL;}else if (parP->_right == parent){parP->_right = parL;}parL->_parent = parP;}}private:Node* _root = nullptr;
};void testRBTree()
{int arr[] = { 5,9,1,2,6,7,3 };RBTree<int, int> rb;for (auto e : arr){rb.insert(make_pair(e, e));}cout << "_IsRBtree:" << rb.IsRBTree() << endl;
}

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

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

相关文章

王道考研计算机网络——传输层

一、传输层概述 复用&#xff1a;发送方不同的应用进程都可以使用同一个传输层的协议来传送数据 分用&#xff1a;接收方的传输层在去除报文段的首部之后能把数据交给正确的应用进程 熟知端口号就是知名端口号0-1023 客户端使用的端口号是动态变化的&#xff0c;不是唯一确定…

某房产网站登录RSA加密分析

文章目录 1. 写在前面2. 抓包分析3. 扣加密代码4. 还原加密 1. 写在前面 今天是国庆节&#xff0c;首先祝福看到这篇文章的每一个人节日快乐&#xff01;假期会老的这些天一直在忙事情跟日常带娃&#xff0c;抽不出一点时间来写东西。夜深了、娃也睡了。最近湖南开始降温了&…

Unity Urp无线延申的网格效果

无线延申的网格 该项目必须是再Urp项目 shader代码实现 Shader "Unlit/infTutorial1" {Properties{_Alpha ("Alpha", Range(0, 0.5)) 0.5}SubShader{Tags{"RenderPipeline""UniversalRenderPipeline""RenderType""…

Pikachu靶场——PHP反序列化漏洞

文章目录 1. PHP反序列化1.1 反序列化代码审计1.2 漏洞防御 1. PHP反序列化 可参考我写的另一篇博客&#xff1a;反序列化漏洞及漏洞复现。 序列化serialize() 序列化说通俗点就是把一个对象变成可以传输的字符串&#xff0c;比如下面是一个对象&#xff1a; class S{publi…

【MySQL】初识数据库

目录 ♫什么是数据库 ♫数据库的分类 ♪关系型数据库 ♪非关系型数据库 ♫显示已有的数据库 ♫创建数据库 ♫删除数据库 ♫使用数据库 ♫数据类型 ♪数值类型 ♪字符串类型 ♪日期类型 ♫创建表 ♫查看表 ♪查看当前数据库下所以表&#xff1a; ♪查看指定表结构&#x…

java多线程相关介绍

1. 线程的创建和启动 在 Java 中创建线程有两种方式。一种是继承 Thread 类并重写其中的 run() 方法&#xff0c;另一种是实现 Runnable 接口并重写其中的 run() 方法。创建完线程对象后&#xff0c;调用 start() 方法可以启动线程。 2. 线程的状态 Java 的线程在不同阶段会处于…

SimpleCG动画示例--汉诺塔动画演示

前言 SimpleCG的使用方法在前面已经介绍了许多&#xff0c;有兴趣的同学如果有去动手&#xff0c;制作一些简单动画应该没多大问题的。所以这次我们来演示一下简单动画。我们刚学习C语言的递归函数时&#xff0c;有一个经典例子相信很多同学都写过&#xff0c;那就是汉诺塔。那…

hadoop生态现状、介绍、部署

一、引出hadoop 1、hadoop的高薪现状 各招聘平台都有许多hadoop高薪职位&#xff0c;可以看看职位所需求的技能 ----> hadoop是什么&#xff0c;为什么会这么高薪&#xff1f;引出大数据&#xff0c;大数据时代&#xff0c;大数据与云计算 2、大数据时代的介绍 大数据的故事…

buuctf-[WUSTCTF2020]CV Maker

打开环境 随便登录注册一下 进入到了profile.php 其他没有什么页面&#xff0c;只能更换头像上传文件&#xff0c;所以猜测是文件上传漏洞 上传一句话木马看看 <?php eval($_POST[a]);?>回显 搜索一下 添加文件头GIF89a。上传php文件 查看页面源代码&#xff0c;看…

【分布式云储存】Springboot微服务接入MinIO实现文件服务

文章目录 前言技术回顾准备工作申请accessKey\secretKey创建数据存储桶公共资源直接访问测试 接入springboot实现文件服务依赖引入配置文件MinIO配置MinIO工具类 OkHttpSSLSocketClient兼容ssl静态资源预览解决方案资源上传预览测试测试结果 前言 上篇博客我们介绍了分布式云存…

Unity之Hololens如何升级MRTK内置shader支持URP

一.前言 什么是Hololens? Hololens是由微软开发的一款混合现实头戴式设备,它将虚拟内容与现实世界相结合,为用户提供了沉浸式的AR体验。Hololens通过内置的传感器和摄像头,能够感知用户的环境,并在用户的视野中显示虚拟对象。这使得用户可以与虚拟内容进行互动,将数字信…

php时间选择器插件与安全过滤参数发生空格冲突

php参数过滤时&#xff0c;将“ ”作为隐患予以禁止&#xff0c;但是在时间传递时&#xff0c;如2023-09-30 10:00:00作为变量传递时&#xff0c;被禁止。 php安全参数过滤 function safe_replace($str) {$disallow_str array(%27, %2527, *, ", "", ;, <…

Qt Creator 预览界面 快捷键

一般来说&#xff0c;我们运行Qt程序所花费的时间是比较长的&#xff0c;那有时我们只改变了界面&#xff0c;那么此时花费如此长的时间去运行程序来观察界面改动的效果是非常浪费时间的行为。 此时我们可以选择预览界面来观察界面改动后的效果&#xff1a;

Java性能调优必备知识学习路线

性能调优是Java开发中一个非常重要的环节&#xff0c;它可以帮助我们提高系统的性能、稳定性、可靠性和用户体验&#xff0c;从而提高用户体验和企业竞争力。 目录 一、为什么要学习Java性能调优&#xff1f; 二、如何做好性能调优&#xff1f; 2.1 扎实的计算机基础 2.2 …

通过Nginx重新认识HTTP错误码

文章目录 概要一、HTTP错误码1.1、1xx1.2、2xx1.3、3xx1.4、4xx1.5、5xx 二、Nginx对常见错误处理三、参考资料 概要 在web开发过程中&#xff0c;通过HTTP错误码快速定位问题是一个非常重要的技能&#xff0c;同时Nginx是非常常用的一个实现HTTP协议的服务&#xff0c;因此本…

数字时代古文的传承———云南文化瑰宝“爨文化“(我为家乡发声)

文章目录 前言⭐ "爨"意味着什么&#xff0c;究竟何为"爨文化"&#xff1f;⭐ 爨文化鲜明的特点1.经济生活2.政治生活3.文化艺术 ⭐ 数字时代古文的传承与传播1.藏品数字化2.建立数据库3.传播大众化 前言 爨文化是继古滇文化之后崛起于珠江正源南盘江流域…

jieba.posseg是jieba中的一个组件,它用于对文本进行词性标注

jieba.posseg是Python中的一个分词工具&#xff0c;它可以将文本切割成词语&#xff0c;并且为每个词语标注词性。这个工具可以帮助我们更好地理解和处理自然语言文本。而pseg则是jieba.posseg的别名&#xff0c;用于方便快捷地进行调用。 下面是一个简单的例子&#xff0c;展…

jvm内存分配与回收策略

自动内存管理 解决两个问题 自动给对象分配内存 对象一般堆上分配&#xff08;而实际上也有可能经过即时编译后被拆散为标量类型并间接地在栈上分配&#xff09; 新生对象通常会分配在新生代&#xff0c;少数情况下&#xff08;例如对象大小超过一定阈值&#xff09;也可能…

Windows下使用VS2010编译出带pdb可调试的FFmpeg库

本人主要在windows环境下开发,Linux下的gpb调试工具又不如vs调试方便(使用过其他调试工具才知道,vs果真为宇宙最强调试工具),所以决定在windows编译可以调试FFmpeg,以方便调试和学习FFmpeg内部代码。 有过在visual studio下编程的小伙伴应该都知道vs的调试信息主要依靠于…

原型、原型链、判断数据类型

目录 作用 原型链 引用类型&#xff1a;__proto__(隐式原型)属性&#xff0c;属性值是对象函数&#xff1a;prototype(原型)属性&#xff0c;属性值是对象 Function&#xff1a;本身也是函数 相关方法 person.prototype.isPrototypeOf(stu) Object.getPrototypeOf(objec…