C++——关联式容器(3):红黑树

news2024/9/20 14:42:07

3.红黑树

3.1 红黑树的概念

        上一篇文章介绍了AVL树,AVL树是解决一般的搜索二叉树效率退化的一种很好的方式。除了AVL树之外,红黑树也是一种非常好的选择。红黑树也是一种搜索二叉树,从其名字上就能够发现红黑树依靠标识红色或黑色来构建整棵树,在安排结点位置时需要考虑到颜色的问题。下面给出红黑树需要满足的条件,同时也是其性质:

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

②红黑树根结点的颜色是黑色

红色结点的两个孩子颜色必须是黑色,即不允许红色结点连续链接

任意一条路径(从根结点到任一叶子结点)上的黑色结点数目都是相等的

⑤叶子结点(指的是空结点)颜色是黑色的。

        通过以上性质,我们很容易的就可以发现一条路径最短的情况就是全为黑色的情况,而最长的情况就是黑红相互交替出现的情况。所以不难发现红黑树满足最长路径不超过最短路径的两倍的这个结论,这也是红黑树维持其搜索效率不退化的原因。

3.2 红黑树详解

3.2.1 红黑树的实现

3.2.1.1 红黑树的结点

        对于一棵红黑树而言,首先它是搜索二叉树,所以需要指向孩子的指针和值域。又因为其结点需要标识红色、黑色,而且如平衡二叉树一样需要特殊情况的旋转处理,所以还需要表示颜色的成员和指向父亲结点的指针。此处将其颜色定义为枚举常量进行管理。

	//定义枚举类型来标识红、黑两种颜色
	enum color {
		RED,
		BLACK
	};

	//红黑树的结点
	template<class K, class V>
	struct RBTreeNode {
		pair<K, V> _pair;
		RBTreeNode<K, V>* _left;
		RBTreeNode<K, V>* _right;
		RBTreeNode<K, V>* _parent;
		color _color;

		RBTreeNode(pair<K, V> kv)
			:_pair(kv)
			, _left(nullptr)
			, _right(nullptr)
			, _parent(nullptr)
			//红黑树要求每条路径的黑色节点数目相等,所以默认插入红色节点更加合理
			, _color(RED)
		{}
	};

        对于结点的构造函数,需要指出的是新结点的默认颜色。由于红黑树要求各路径下的黑色节点树木相同,因此如果插入的是黑色节点,则必然会打破这个规则;而如果插入红色结点,则有可能违反不允许连续红色结点规则,但是仅是有可能。因此两害相权取其轻,我们选择红色结点作为默认插入的颜色。

3.2.1.2 默认成员函数

        红黑树的默认成员函数和二叉搜索树也几乎一致。拷贝构造用前序遍历的方式;赋值重载复用拷贝构造;析构函数用后序遍历的方法来析构结点。

	template<class K, class V>
	class RBTree {
		typedef RBTreeNode<K, V> RBNode;

	public:
		//无参构造
		RBTree()
			:_root(nullptr)
		{}

		//拷贝构造
		RBTree(const RBTree<K, V>& rb)
		{
			_root = copy(rb._root);
		}
	private:
		RBNode* copy(RBNode* root)
		{
			if (root == nullptr) return nullptr;
			RBNode* newnode = new RBNode(root->_pair);
			newnode->_left = copy(root->_left);
			newnode->_right = copy(root->_right);
			return newnode;
		}

	public:
		//析构函数
		~RBTree()
		{
			destroy(_root);
			_root = nullptr;
		}
	private:
		void destroy(RBNode* root)
		{
			if (root == nullptr) return;
			destroy(root->_left);
			destroy(root->_right);
			delete root;
		}

	public:
		//赋值重载操作符
		RBTree<K, V>& operator=(const RBTree<K, V> rb)
		{
			swap(_root, rb->_root);
			return *this;
		}
	private:
		RBNode* _root;
	};

3.3 插入结点——旋转变色

        同样的,如何处理新节点的插入逻辑也是红黑树的难点之一,红黑树的插入主要用到的调整方法也是在AVL树中介绍过的单旋和双旋,然后再进行颜色的调整。

3.3.1 第一步——插入结点

        第一步依旧是喜闻乐见的插入结点操作,搜索二叉树都是这个插入方法,找到对应位置将结点链接上去即可。

3.3.2 第二步——调整红黑树的颜色

        我们提前约定好各个可能涉及到的结点的命名,以便后文的叙述。如图所示,以新结点为标准,其父亲结点我们成为parent,简称p;p的兄弟节点称作uncle,简称u;而p和u的父结点则是grandparent,简称g。

        我们在上文提到了,插入红色结点后可能由于新结点的父结点是黑色而没有违反规则,无需调整的情况。所以当且仅当p也为红色时,这时新结点和parent形成了连续的红色,需要进行调整。

        在调整的过程中,我们可以将所有可能出现的情况总结为两类。下面将针对这两种情况一一讨论,在讨论中需要时刻关注到任意一条路径上的黑色结点数目都是相等的这一特性。

        a. u为红色

        当看到有两个连续的红结点,第一想法肯定是寻找能否直接变色。面对u为红色的情况,可以发现左子树p和右子树均为红色,因此当同时使p和u变为黑色后,以g为根的整颗子树黑色结点数目都+1,为了保持黑色节点数目不变,所以将黑色的g变为红色(因为p和u是红色的,所以g一定是黑色),这样就维持了黑色节点数目稳定。

        但是这样会产生另一个问题,即g变为红色,可能g的父亲也是红色,这就又导致了连续的红结点。不过处理方法也不难,就是将g作为新的cur,然后对应着此处总结的三种方法去变换即可,即为一个循环的过程。

        b. u为黑色或u不存在

        当u为黑色或不存在时,说明不可以再同时变色了(因为u那边没有现成的红色节点),所以需要提供其他办法。于是就请到了二叉搜索树的传统方法:旋转,通过合理的旋转改变两个红色结点的位置来消除连续红结点的问题。于是我们可以继续细分为四种处理防方式。

        四种处理方式又可以根据位置关系分为单旋和双选,当g、p和cur是顺位(左左或右右)时,仅一次单旋即可;当g、p和cur是逆位(左右或右左)时,则需要双旋

        ①左左顺位——右旋,p变黑,g变红

        ②右右顺位——左旋,p变黑,g变红

        ③左右逆位——左右双旋,cur变黑,g变红

        ④右左逆位——右左双旋,cur变黑,g变红

3.3.3 代码整理

        最后给出插入逻辑以及颜色调整的完整代码。

		//插入
		bool Insert(const pair<K, V>& kv)
		{
			//第一个结点特殊处理
			if (_root == nullptr)
			{
				_root = new RBNode(kv);
				_root->_color = BLACK;
				return true;
			}

			RBNode* cur = _root;
			RBNode* parent = nullptr;
			while (cur)
			{
				if (cur->_pair.first > kv.first)
				{
					parent = cur;
					cur = cur->_left;
				}
				else if (cur->_pair.first < kv.first)
				{
					parent = cur;
					cur = cur->_right;
				}
				else
				{
					return false;
				}
			}
			cur = new RBNode(kv);
			if (parent->_pair.first > kv.first)
			{
				parent->_left = cur;
				cur->_parent = parent;
			}
			else
			{
				parent->_right = cur;
				cur->_parent = parent;
			}

			//调整红黑树颜色
			//红黑树规则:
			// ①根结点颜色一定是黑色
			// ②不能出现连续的红结点,即红结点的孩子一定是黑色
			// ③各条路径(根结点->叶子结点)上的黑色节点数目相同
			// ④叶子结点(此处认为是空结点)颜色为黑色
            //在这样的规则限制下,不难发现红黑树最长路径一定小于最短路径的二倍这个特征
			
			//当违反了红黑树规则才需要调整红黑树颜色
			//插入新的结点时,选择插入红色节点可能违反不能有连续的红色节点的规则;选择插入黑色节点则必然会违反黑色节点数目相同的规则
			//因此两害相权取其轻,选择插入红色节点,因此我们主要处理的就是连续红结点的问题
			//于是连续的两个节点:cur和p都是红色的,而u作为p的兄弟节点决定了调整方式,而在调整中受影响的则是p和u的父结点g
			while (parent && parent->_color == RED)
			{
				//根据形式的不同,一般分为三类处理
				//在解决连续红色的问题时,也要兼顾到黑色节点数目相同这一规则
				RBNode* grandparent = parent->_parent;
				RBNode* uncle = parent == grandparent->_left ? grandparent->_right : grandparent->_left;
				//①u为红色(p、u均为红)
				//p、u同时变为黑色,g变为红色,因为g是红色,因此需要继续向上检查
				if (uncle && uncle->_color == RED)
				{
					parent->_color = uncle->_color = BLACK;
					grandparent->_color = RED;
					parent = grandparent->_parent;
					cur = grandparent;
				}
				//②u为黑色或不存在,而g、p和cur是顺位(左左或右右)
				//此时单纯的变色会使得p子树和u子树路径黑色节点数目不同(因为在修改p为黑,u本就为黑,u相较p黑色节点少一个)
				//为了可以顺利变色,我们首先要旋转,红色的p成为了子树的根,黑色的g成为了u这棵树的父结点,此时可以证明只需要p变为黑,g变为红即可
				//旋转操作就是AVL树中的左右单旋

				//③u为黑色或不存在,而g、p和cur是逆位(左右或右左)
				//此时只需要将p结点左旋或右旋一次即可形成如②的情况,因此这种情况使用双旋即可
				else
				{
					if (parent == grandparent->_left)
					{
						//左左顺位——右旋,p变黑,g变红
						if (cur == parent->_left)
						{
							RotateR(grandparent);
						}
						//左右逆位——左右双旋,cur变黑,g变红
						else
						{
							RotateLR(grandparent);
						}
					}
					else
					{
						//右右顺位——左旋,p变黑,g变红
						if (cur == parent->_right)
						{
							RotateL(grandparent);
						}
						//右左逆位——右左双旋,cur变黑,g变红
						else
						{
							RotateRL(grandparent);
						}
					}
					//由于②③结果的子树根结点都是黑色因此不会影响上一层,无需向上检查
					break;
				}
			}
			//根结点有可能变色,需要修改
			_root->_color = BLACK;
			return true;
		}
	private:
		void RotateL(RBNode* grandparent)
		{
			RBNode* subR = grandparent->_right;
			RBNode* subRL = subR->_left;

			//结点链接三组:subR和grandparent、grandparent和sunRL、grandparent->_parent和subR
			subR->_left = grandparent;
			grandparent->_right = subRL;
			if (grandparent->_parent == nullptr)
			{
				_root = subR;
			}
			else if (grandparent->_parent->_left == grandparent)
			{
				grandparent->_parent->_left = subR;
			}
			else
			{
				grandparent->_parent->_right = subR;
			}

			subR->_parent = grandparent->_parent;
			grandparent->_parent = subR;
			if (subRL)	//右左子树为空树
				subRL->_parent = grandparent;

			//修改颜色:p变黑,g变红
			subR->_color = BLACK;
			grandparent->_color = RED;
		}

		void RotateR(RBNode* grandparent)
		{
			RBNode* subL = grandparent->_left;
			RBNode* subLR = subL->_right;

			//结点链接三组:subL和grandparent、grandparent和sunLR、grandparent->_parent和subL
			subL->_right = grandparent;
			grandparent->_left = subLR;
			if (grandparent->_parent == nullptr)
			{
				_root = subL;
			}
			else if (grandparent->_parent->_left == grandparent)
			{
				grandparent->_parent->_left = subL;
			}
			else
			{
				grandparent->_parent->_right = subL;
			}

			subL->_parent = grandparent->_parent;
			grandparent->_parent = subL;
			if (subLR)	//左右子树为空树
				subLR->_parent = grandparent;

			//修改颜色:p变黑,g变红
			subL->_color = BLACK;
			grandparent->_color = RED;
		}

		//左右双旋
		void RotateLR(RBNode* grandparent)
		{
			RBNode* subL = grandparent->_left;
			RBNode* subLR = grandparent->_left->_right;

			//只需要旋转,颜色最后指定
			RotateL(subL);
			RotateR(grandparent);

			//修改颜色:cur变黑,g变红
			subLR->_color = BLACK;
			grandparent->_color = RED;
		}

		//右左双旋
		void RotateRL(RBNode* grandparent)
		{
			RBNode* subR = grandparent->_right;
			RBNode* subRL = grandparent->_right->_left;

			//只需要旋转,颜色最后指定
			RotateR(subR);
			RotateL(grandparent);

			//修改颜色:cur变黑,g变红
			subRL->_color = BLACK;
			grandparent->_color = RED;
		}

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

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

相关文章

集成显卡与独立显卡之间的区别,以及如何选择?

目录 一、集成显卡介绍 二、独立显卡介绍 三、性能与功耗对比 四、应用场景与选购指南 五、总结 大家在选购电脑的时候,显卡作为图形处理的关键设备,对大型3D游戏、图形设计类软件运行非常关键。目前电脑显卡主要分为两大类:集成显卡与独立显卡。今天给大家聊聊集成显卡…

Jeremy Howard对创业,AI产品,技术趋势,社区的看法

Jeremy Howard&#xff0c;一位在人工智能领域留下深刻印记的科学家和教育家&#xff0c;以其对深度学习的普及和教育的执着追求而闻名。出生于澳大利亚的他&#xff0c;不仅在学术界取得了令人瞩目的成就&#xff0c;更是将复杂的机器学习技术带给了更广泛的公众。 在悉尼大学…

Amazon EC2:引领企业迈向云计算的未来

在数字化转型的浪潮中&#xff0c;企业需要一个强大、灵活且安全的计算平台来支持其不断变化的业务需求。Amazon Elastic Compute Cloud&#xff08;EC2&#xff09;正是这样一个解决方案&#xff0c;它为企业提供了一个可扩展的云计算环境&#xff0c;帮助企业实现高效、低成本…

【课程系列11】某客时间AI 大模型应用开发实战营

某客时间AI 大模型应用开发实战营 链接 百度网盘&#xff0c;链接&#xff1a;https://pan.baidu.com/s/1ZC-fOC_QQjNM6wyVjQcYOg 课程详情 https://u.geekbang.org/subject/llm/ 初探大模型&#xff1a;起源与发展 预热篇&#xff1a;解码注意力机制&#xff08;Attentio…

Deep Active Contours for Real-time 6-DoF Object Tracking

这篇论文解决了从RGB视频进行实时6自由度&#xff08;6-DoF&#xff09;物体跟踪的问题。此前的基于优化的方法通过对齐投影模型与图像来优化物体姿态&#xff0c;这种方法依赖于手工设计的特征&#xff0c;因此容易陷入次优解。最近的基于学习的方法使用神经网络来预测姿态&am…

等保测评中的访问控制策略:企业优化指南

在信息安全等级保护&#xff08;等保&#xff09;测评中&#xff0c;访问控制作等保测评中的访问控制策略&#xff1a;企业优化指南为保障信息系统安全的关键环节&#xff0c;其策略的合理性和有效性直接影响到测评结果。企业如何优化访问控制策略&#xff0c;以满足等保测评的…

21432423

c语言中的小小白-CSDN博客c语言中的小小白关注算法,c,c语言,贪心算法,链表,mysql,动态规划,后端,线性回归,数据结构,排序算法领域.https://blog.csdn.net/bhbcdxb123?spm1001.2014.3001.5343 给大家分享一句我很喜欢我话&#xff1a; 知不足而奋进&#xff0c;望远山而前行&am…

NC 兑换零钱(一)

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 描述 给定数组arr&…

VMware Workstation Player虚拟机Ubuntu启用Windows共享目录

1、新建共享目录 2、安装并启用vmtools、fuse sudo apt update sudo apt install open-vm-tools open-vm-tools-desktop sudo apt install fuse sudo systemctl start open-vm-tools sudo systemctl enable open-vm-tools 3、命令挂载 sudo vmhgfs-fuse .host:/SharedFold…

svg画进度条

直接返回一个进度条的组件&#xff0c;代码如下所示&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0">…

vscode+vue3+vite项目配置stylelint 2024版本

项目场景&#xff1a; 在搭建前端项目时经常要用到一些工程化的东西&#xff0c;还有一个规范化的东西&#xff0c;现在eslint已经在各大脚手架工具集成的很好&#xff0c;但是stylelint还是有点欠缺&#xff0c;而且每次的版本更新迭代是个最让人头疼的问题&#xff0c;写这篇…

uniapp+vue3 使用canvas,并保存图片(图片是空白的问题)

首先把其中的要点先说一下 uni.createCanvasContext(canvasId, componentInstance) 当我们没有使用自定义组件时, 参数 componentInstance 可以不传 但如果我们的 canvas 是放在自定义组件中, 在vue2中一般传的是 this , 在vue3中是没有this的, 当我们在 vue3中要使用 th…

亚数TrustAsia亮相第十四届智慧城市与智能经济博览会,入围“2024数据要素创新应用优秀成果”!

智博会 2024年9月6日至8日&#xff0c;由宁波市人民政府、浙江省经济和信息化厅、中国信息通信研究院、中国电子信息行业联合会、中国电信、中国移动、中国联通主办的2024世界数字经济大会暨第十四届智慧城市与智能经济博览会&#xff08;以下简称“智博会”&#xff09;在宁波…

SpringCloud-03 LoadBalancer服务调用与负载均衡

LoadBalancer&#xff08;负载均衡器&#xff09;是一种网络设备或软件&#xff0c;用于将网络流量分配到多个服务器&#xff0c;以实现负载均衡和高可用性。它可以帮助确保网络服务器能够有效地处理大量的请求&#xff0c;并避免某些服务器过载而导致性能下降或服务不可用。 …

CCRC-DSO数据安全官评AI:烧出的热度?美丽的泡沫?

中国AI产业的热潮持续高涨&#xff0c;这主要显现在企业获得融资的速度和规模上。 近期&#xff0c;智谱AI这家大模型独角兽公司完成了本年度的第三次融资&#xff0c;其投资前估值已达到惊人的200亿元&#xff1b;同时&#xff0c;无问芯穹也完成了接近5亿元的A轮融资&#x…

# 欢迎使用Markdown编辑器

这里写自定义目录标题 欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants 创建一个自定义列表如何创建一个…

使用单个位存储结点颜色在深度优先搜索(DFS)中的充分性证明

使用单个位存储结点颜色在深度优先搜索(DFS)中的充分性证明 证明思路证明过程C语言实现结论在图论中的深度优先搜索(DFS)算法中,通常使用“颜色”来标记结点的访问状态。传统上,可以使用三种颜色: 白色:表示结点未被访问。灰色:表示结点正在被访问(即已访问但其邻接结…

再次进阶 舞台王者 第八季完美童模全球赛首席体验官【雷璨旖】赛场秀场超燃合集!

7月20-23日&#xff0c;2024第八季完美童模全球总决赛在青岛圆满落幕。在盛大的颁奖典礼上&#xff0c;一位才能出众的少女——雷璨旖&#xff0c;迎来了她舞台生涯的璀璨时刻。 首席体验官——雷璨旖&#xff0c;以璀璨童星之姿&#xff0c;优雅地踏上完美童模盛宴的绚丽舞台&…

Java面试篇基础部分-Java中的异常以及异常处理

导语   在实际的开发过程中,往往会遇到各种各样的编程异常,如何处理这些异常,直接会影响到整个程序和系统的稳定性,如果不能在合适的地方抛出合适的异常或者是对异常进行捕获。那么就会影响到整个程序的运行。所以如何处理异常,是作为每个开发者来说必不可少的开发技能。…

电脑开机速度慢怎么解决?

电脑开机速度慢怎么解决&#xff1f;电脑开机速度慢的原因可以是多方面的&#xff0c;以下是一些常见的原因&#xff1a; 启动项过多&#xff1a; 许多软件在系统启动时会自动启动&#xff0c;导致启动项过多&#xff0c;从而延长了开机时间。过时的驱动程序&#xff1a; 设备…