红黑树——封装map和set

news2024/11/15 20:01:31

概念

红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。

  1. 每个结点不是红色就是黑色
  2. 根结点是黑色
  3. 树不能出现连续的红色结点
  4. 同一层的结点的路径(这里的路径是指该结点到null结点的路径),包含相同数量的黑色结点
  5. 每一个null结点都是黑色的
    在这里插入图片描述

为什么上面的逻辑可以控制高度使搜索树平衡?

基于条件3和条件4,我们可以推出最短路径肯定是全黑结点最长路径肯定是红黑相间的路径,保证树的左右子树高度高度在h~2h之间,限定了高度差不超过2。如图:
在这里插入图片描述

红黑树与AVL树的区别?

红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O( l o g 2 N log_2 N log2N),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。

红黑树节点的定义

enum color
{
	RED,
	BLACK
};//枚举的使用必须先初始化

template<class Value>
struct RBTreeNode
{
	RBTreeNode(const Value& data)
		:_leftNode(nullptr)
		, _rightNode(nullptr)
		, _parentNode(nullptr)
		, _data(data)
		,_col(RED)
	{

	}

	Value _data;
	RBTreeNode<Value>* _leftNode;
	RBTreeNode<Value>* _rightNode;
	RBTreeNode<Value>* _parentNode;
	color _col;
};

标准的三叉链(左右子树指针和父结点指针),模版的含义后面封装map,set的时候再详述

红黑树的插入调整讲述

前面是搜索树的插入逻辑,与之前搜索树不同的是后面的根据红黑色节点的旋转调整。

首先,我们的新增结点颜色一定是红色,为什么呢?
因为插入红色,对于整棵树的影响比较小新增插入黑色节点会影响所有路径:-规则4
新增插入红色节点会影响父亲结点-规则3

检测新节点插入后,红黑树的性质是否造到破坏
因为新节点的默认颜色是红色,因此:如果其双亲节点的颜色是黑色,没有违反红黑树任何性质,则不需要调整;但当新插入节点的双亲节点颜色为红色时,就违反了性质三不能有连在一起的红色节点,约定:cur为当前节点,pcur为父节点,gcur为祖父节点,ucur为叔叔节点
此时需要对红黑树分情况来讨论:

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

解决方案:把pcur和ucur变成黑色,把gcur变成红色,然后cur = gcur继续往上调整,在代码层面,迭代时用cur和pcurj,直到pcur为黑色或者走到根结点。
在这里插入图片描述

对于根节点的处理:

如上图中的第一棵树,在调整中会把根节点调成红色,这显然不符合根节点的定义,对于这情况只需在逻辑退出后把根节点调整为黑即可。

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

解决方案:
1.右单旋:p为g的左孩子,cur为p的左孩子,则进行右单旋转,p、g变色–p变黑,g变红
2.左单旋:p为g的右孩子,cur为p的右孩子,则进行左单旋转,p、g变色–p变黑,g变红
3.左右单旋:p为g的左孩子,cur为p的右孩子,则先进行左单旋,再右单旋
4.右左单旋:p为g的右孩子,cur为p的左孩子,则先进行右单旋,再左单旋

该图为右单旋
在这里插入图片描述
写旋转的技巧:
1.先想象简单的三个点,然后有4种情况,如图:
在这里插入图片描述
2.然后再去延伸到有左右子树和该树作为子树的思路去延伸(三角形为抽象树,把他想象成一个子树即可)
在这里插入图片描述
把新增结点设为cur,新增结点的父结点设为pcur,还有pcur父结点gcur,对于左旋转来说,需要操作的结点为gcurParent(祖父结点的父结点),用于旋转后让cur继续于上面的树保持链接,pcur结点,cur结点,还有cur结点的右子树,因为要连接到pcur结点左子树,而pcur结点要变成cur结点右子树(详细的选择逻辑在我AVL树的文章里面)所以要操作的结点为 gcur、pcur、gcurParent、pcurRight。右旋转就是gcur、pcur、gcurParent、pcurLeft
左转和右转的快速判断:
gcur移动到左边就是左转,其目的是右边较高的部分移到左边,使得树平衡gcur移动到右边就是右转,其目的是左边较高的部分移到右边,使得树平衡

class RBTree
{
public:
	pair<iterator,bool> insert(const V& data)
	{
		//找位置
		//BST的插入逻辑(右大左小)
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;
			return make_pair(iterator(_root), true);
		}
		Node* pcur = nullptr;
		Node* cur = _root;
		KeyOfT kot;
		while (cur)
		{
			pcur = cur;//记录cur结点的父结点
			if (kot(data) > kot(cur->_data))
			{
				cur = cur->_rightNode;
			}
			else if (kot(data) < kot(cur->_data))
			{
				cur = cur->_leftNode;
			}
			else//重复的不让插入
			{
				return make_pair(iterator(cur), false);
			}
		}
		cur = new Node(data);
		Node* newnode = cur;//用于返回

		//链接
		cur->_parentNode = pcur;//孩子结点连接父结点
		//父亲结点连接孩子结点
		if (cur->_data.first > pcur->_data.first)
		{
			pcur->_rightNode = cur;
		}
		else
		{
			pcur->_leftNode = cur;
		}
		
		//如果cur节点和pcur节点都为红色--调整
		while (pcur && pcur->_col == RED)
		//判断pcur是否为空,避免对空指针的解引用
		{
			//cout << data.first << endl;
			//获取ucur和gcur
			Node* ucur = nullptr;
			Node* gcur = pcur->_parentNode;
			if (gcur->_leftNode != pcur)
			{
				ucur = gcur->_leftNode;
			}
			else
			{
				ucur = gcur->_rightNode;
			}

			//情况一:
			if (ucur && ucur->_col == RED)
			{
				pcur->_col = ucur->_col = BLACK;
				gcur->_col = RED;

				cur = gcur;
				pcur = cur->_parentNode;
			}
			else if(ucur == nullptr || ucur->_col == BLACK)
			{
				//判断gcur, pcur, cur之间的连接情况
				if (gcur->_leftNode == pcur && pcur->_leftNode == cur) //右单旋
				{
					_rotateR(gcur,pcur);
				}
				else if (gcur->_rightNode == pcur && pcur->_rightNode == cur) //左单旋
				{
					_rotateL(gcur, pcur);
				}
				else if (gcur->_leftNode == pcur && pcur->_rightNode == cur) //右左旋
				{
					 _rotateL(pcur, cur);
					 //第一次旋转后pcur发生了变换 cur才是pcur
					 _rotateR(gcur, cur);
				}
				else if (gcur->_rightNode == pcur && pcur->_leftNode == cur) //左右旋
				{
					_rotateR(pcur, cur);
					_rotateL(gcur, cur);
				}
				else
				{
					cout << data.first << endl;
					assert(false);
				}
			}
			else
			{
				assert(false);
			}
			_root->_col = BLACK;
		}
		return make_pair(iterator(newnode), true);
	}
private:
	Node* _root = nullptr;
	void _rotateR(Node* gcur,Node* pcur)
	{
		Node* gcurParent = gcur->_parentNode;
		Node* pcurRight = pcur->_rightNode;
		//修改parentNode
		pcur->_parentNode = gcurParent;
		gcur->_parentNode = pcur;
		if(pcurRight)
			pcurRight->_parentNode = gcur;

		//修改leftNode和rightNode
		pcur->_rightNode = gcur;
		gcur->_leftNode = pcurRight;
		if (gcurParent == nullptr)
		{
			_root = pcur;
			_root->_parentNode = nullptr;
		}
		else if (gcurParent->_leftNode == gcur)
		{
			gcurParent->_leftNode = pcur;
		}
		else
		{
			gcurParent->_rightNode = pcur;
		}
		pcur->_col = BLACK;
		gcur->_col = RED;
	}

	void _rotateL(Node* gcur, Node* pcur)
	{
		Node* gcurParent = gcur->_parentNode;
		Node* pcurLeft = pcur->_leftNode;
		//修改parentNode
		pcur->_parentNode = gcurParent;
		gcur->_parentNode = pcur;
		if(pcurLeft)
			pcurLeft->_parentNode = gcur;

		//修改leftNode和rightNode
		pcur->_leftNode = gcur;
		gcur->_rightNode = pcurLeft;
		if (gcurParent == nullptr)
		{
			_root = pcur;
			_root->_parentNode = nullptr;
		}
		else if(gcurParent->_leftNode == gcur)
		{
			gcurParent->_leftNode = pcur;
		}
		else
		{
			gcurParent->_rightNode = pcur;
		}
		pcur->_col = BLACK;
		gcur->_col = RED;
	}

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

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

相关文章

MySQL数据库介绍——初始数据库MySQL

作者简介&#xff1a;一名云计算网络运维人员、每天分享网络与运维的技术与干货。 公众号&#xff1a;网络豆云计算学堂 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a; 网络豆的主页​​​​​ 目录 写在前面&#xff1a; 一.数据库基础知识 1.…

使用shell脚本安装mysql8,进行主从备份配置

思路 在3台主机上安装mysql进行主从备份配置 使用rpm包yum安装mysql 首先&#xff0c;我们要准备好安装文件&#xff0c;首先下载rpm包 wget -P "/opt/" https://repo.mysql.com//mysql80-community-release-el7-3.noarch.rpm 然后执行安装&#xff08;默认已配置…

1111111111111113

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &#x1f4e2;本文由 JohnKi 原创&#xff0c;首发于 CSDN&#x1f649; &#x1f4e2;未来很长&#…

openwrt的旁路模式无法访问国内网站

防火墙: 常规设置-> 区域&#xff1a; lan-> wan :编辑 IP 动态伪装:勾选

【Qt线程】—— Qt线程详解

目录 &#xff08;一&#xff09;多线程的概述 &#xff08;二&#xff09;Qt线程的使用条件 &#xff08;三&#xff09;创建线程的方法 3.1 继承QTread&#xff0c;重写run()函数 3.1.1 为什么要重写 3.2 继承QObject 3.3 核心API介绍 3.4 关闭线程的使用方法 &…

高压挑战:新能源汽车换电连接器的技术革新

摘要 随着汽车行业的电动化、网联化和智能化发展&#xff0c;新能源汽车连接器的使用量从传统汽车的600个左右增加到800至1000个。新能源汽车连接器在电连接和信号连接方面更为复杂&#xff0c;包括低压连接器和高压连接器。高压连接器面临严苛性能要求&#xff0c;如耐热性、…

Tomcat控制台乱码问题已解决(2024/9/7

步骤很详细&#xff0c;直接上教程 问题复现&#xff1a; 情景一 情景二 原因简述 这是由于编码不一致引起的&#xff0c;Tomcat启动后默认编码UTF-8&#xff0c;而Windows的默认编码是GBK。因此你想让其不乱码&#xff0c;只需配置conf\logging.properties的编码格式即可 解决…

探索Pyro4:Python中的远程对象通信艺术

文章目录 探索Pyro4&#xff1a;Python中的远程对象通信艺术背景&#xff1a;为何选择Pyro4&#xff1f;Pyro4是什么&#xff1f;如何安装Pyro4&#xff1f;简单的库函数使用方法场景应用示例常见Bug及解决方案总结 探索Pyro4&#xff1a;Python中的远程对象通信艺术 背景&…

git中,隐藏application.properties文件,修改不用提交了

git中&#xff0c;隐藏application.properties文件&#xff0c;修改不用提交了 A、将文件名放入 .gitignore 文件中 B、执行git命令隐藏文件 执行在ide上执行命令 a、执行隐藏命令 git rm --cached src/main/resources/application.properties b、执行提交命令 git commit -m…

【生日视频制作】劳斯莱斯库里南中控改名软件AE模板修改文字软件生成器教程特效素材【AE模板】

生日视频制作教程豪车劳斯莱斯库里南中控改名软件AE模板修改文字特效广告生成神器素材祝福玩法AE模板工程 怎么如何做的【生日视频制作】劳斯莱斯库里南中控改名软件AE模板修改文字软件生成器教程特效素材【AE模板】 生日视频制作步骤&#xff1a; 下载AE模板 安装AE软件 把A…

120张网络安全等保拓扑大全

120张网络安全等保拓扑大全已更新至星球&#x1f517;哦&#xff0c;有兴趣的领取吧。

处理List采用并行流处理时,通过ForkJoinPool来控制并行度失控的问题

在使用parallelStream进行处理list时&#xff0c;如不指定线程池&#xff0c;默认的并行度采用cpu核数进行并行&#xff0c;这里采用ForJoinPool来控制&#xff0c;但循环中使用了redis获取key时&#xff0c;出现失控。具体上代码。 RunWith(SpringRunner.class) SpringBootTe…

OpenFeign的使用(一)

OpenFeign的定义 OpenFeign是一个声明式的Web服务客户端&#xff0c;它简化了编写Web服务客户端的过程&#xff0c;使得微服务间的通信更加简单和灵活。它主要作用于帮助开发者方便地调用远程服务&#xff0c;让远程调用像本地方法调用一样简单。 事实上&#xff0c;远程调用的…

共享单车轨迹数据分析:以厦门市共享单车数据为例(一)

共享单车数据作为交通大数据的一个重要组成部分&#xff0c;在现代城市交通管理和规划中发挥着越来越重要的作用。通过对共享单车的数据进行深入分析&#xff0c;城市管理者和规划者能够获得大量有价值的洞察&#xff0c;这些洞察不仅有助于了解城市居民的日常出行模式&#xf…

入职国企3个月,还没碰过代码,很焦虑。。

国企 日常逛脉脉&#xff0c;看到名为「入职后发现提升很慢怎么办」的话题。 本以为是正儿八经的讨论帖&#xff0c;结果点开&#xff0c;还是有凡尔赛&#xff0c;不愧是人均 P8 交流地 &#x1f923;&#x1f923; 一位网友表示&#xff1a;自己入职了国企三个月&#xff0c;…

spring cloud gateway配置

1:Intellij 新建项目 spring-cloud-gateway 2:pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLoca…

正规式与有限自动机例题

答案&#xff1a;D 知识点&#xff1a; 正规式 正规集 举例 ab 字符串ab构成的集合 {ab} a|b 字符串a,b构成的集合 {a,b} a^* 由0或者多个a构成的字符串集合 {空,a,aa,aaa,aaaa} (a|b)^* 所有字符a和b构成的串的集合 {空,a,b,ab,aab,aba,aaab} a(a|b)^* 以a为…

145-Linux权限维持Rootkit后门Strace监控Alias别名Cron定时任务

参考 【权限维持】Linux&Rootkit后门&Strace监控&Alias别名&Cron定时任务_alias lsalerts(){ ls $* --colorauto;python -c "-CSDN博客 参考 FlowUs 息流 - 新一代生产力工具 权限维持-Linux-定时任务-Cron后门 利用系统的定时任务功能进行反弹Shell 1…

电脑WLan无线网连上没网络的问题解决方法

在公司的酒店&#xff0c;网络很差&#xff0c;每到周末网就很差&#xff0c;昨天网上还能上头条写文章&#xff0c;终于&#xff0c;今天浏览器都上不去了&#xff0c;咋回事呢&#xff1f; 明明手机能上网呀&#xff0c;明明电脑显示无线网已连接&#xff0c;没一点问题呀&a…

【语音告警】博灵智能语音报警灯JavaScript循环播报场景实例-语音报警灯|声光报警器|网络信号灯

功能说明 本文将以JavaScript代码为实例&#xff0c;讲解如何通过JavaScript代码调用博灵语音通知终端 A4实现声光语音告警。主要博灵语音通知终端如何实现无线循环播报或者周期播报的功能。 本代码实现HTTP接口的声光语音播报&#xff0c;并指定循环次数、播报内容。由于通知…