C++模拟实现——红黑树

news2024/10/5 17:15:51

一、介绍

红黑树也是对一般的搜索二叉树不能保证平衡的一个改进,和AVL树采用的思路不同,但同样需要旋转,其本质也是一颗平衡搜索二叉树,其节点有颜色的区分,并且被一些规则束缚,在这些规则下,能够使得树最长路径的长度不会高于最短路径的两倍

二、红黑树的性质

1.红黑树的节点,不是红色,就是黑色

2.根节点是黑色的

3.路径上不能出现两个连续的红色节点

4.每条路径上的黑色节点数量相同

5.每个叶子节点指向的空节点,默认认为是黑色的

遵循以上规则,则可以保证最长的路径的长度不会超过最短路径的两倍,因此红黑树实现平衡的核心,就是对新插入的节点使其通过一系列操作,满足上面的五个条件即可

三、红黑树的定义

插入的节点默认为红色,是为了能够调整,插入红色,可能只需要局部调整,若是黑色,则每次插入都必然会影响所有路径

四、红黑树的核心实现Insert

1.基本框架

先是插入数据,然后再是根据规则做出调整,红黑树实现的核心就在于如何调整,使得各种情况的插入都可以调整成符合规则的样子

2.调整分析

情况一:当插入一个新的节点cur为根节点,则直接插入,并且将颜色改为黑色即可
(根节点为黑色)

情况二:继续插入,若是parent节点为黑色,则插入一个红色节点不会破坏规则,因此无需调整
(不能有连续的红色节点,每个路径黑色节点相同)

情况三:插入cur节点,其parent节点也是红色,则出现了两个连续的红色节点,需要根据不同情况进行分类调整:

(1).uncle存在且为红

处理:将parent和uncle变黑,将grandfather变红,然后继续向上调整,cur指向g
parent指向g->_parent

p和u同时变黑,使得g左右两边路径同时增加了一个黑色节点,因此需要将g变红,这样既不影响黑色节点的规则,也没有连续出现的红色节点,但是由于g变红,因此可能会对上面部分造成影响,所以需要继续向上调整,将cur指向grandfather,parent指向g的parent往上继续检查

(2)uncle不存在或者存在且为黑

处理:根据具体情况进行旋转(单旋、双旋),旋转后再将局部最上方的变黑,grandfather变红

旋转即降了高度,并且旋转后通过调整颜色,使得局部内同时符合黑色和红色的约束条件

需要特殊处理的就只有这两种情况,但在代码实现的角度,需要对这两种情况进行细分,首先是先判断parent在grandfather的左边还是右边,这个影响着旋转往哪边旋,然后再是分“叔叔存在且为红”和“叔叔不在或者在且为黑”这两种情况分类讨论,但是处理的思路是一样的,只是在细节上要根据具体情况进行调整,在“叔叔不在或者在且为黑”的条件下,需要继续细分cur在parent的左边还是右边,确定具体是单旋还是双旋,旋转后再变色即可

3.代码实现部分分析

4.具体代码

	bool Insert(const T& data)
	{
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;
			return true;
		}
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (data < cur->_data)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (data > cur->_data)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (data == cur->_data)
			{
				return false;
			}
		}
		cur = new Node(data);
		if (data < parent->_data)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		cur->_parent = parent;

		//插入完成后检查是否需要调整
		while (parent && parent->_col == RED)
		{
			Node* grandfather = parent->_parent;
			if (grandfather->_left == parent)//情况一,叔叔存在且为红
			{
				Node* uncle = grandfather->_right;
				if (uncle && uncle->_col == RED)
				{
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;

					cur = grandfather;
					parent = grandfather->_parent;
				}
				else//叔叔不存在或者存在为黑
				{
					if (parent->_left == cur)//情况二
					{
						RotateR(grandfather);
						grandfather->_col = RED;
						parent->_col = BLACK;
					}
					else//情况三
					{
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break;
				}
			}
			else//grandfather->right == parent
			{
				Node* uncle = grandfather->_left;
				if (uncle && uncle->_col == RED)
				{
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;

					cur = grandfather;
					parent = grandfather->_parent;
				}
				else
				{
					if (parent->_right == cur)//情况二
					{
						RotateL(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break;
				}
			}
		}
		//调整完后,将根重新置为黑色,避免某次调整到根时,将根变成了红色
		_root->_col = BLACK;
		return true;
	}

五、测试接口

在实现完Insert接口后,我们需要实现一些用于测试的接口,验证是否为红黑树

中序遍历InOrder

首先是提供一个中序遍历,但中序遍历只能验证是否是搜索二叉树,并不能保证一定是红黑树

	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}
	void _InOrder(const Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		_InOrder(root->_left);
		cout << root->_data << " ";
		_InOrder(root->_right);
	}

验证红黑树IsBalance

验证是否是红黑树,取决于树是否遵循着红黑树的规则,因此我们需要根据规则去写个函数去检查树是否为红黑树

	//测试是否为红黑树
	bool IsBalance()
	{
		if (_root->_col == RED)
		{
			return false;
		}
		int Reference = -1;
		return _Check(_root,0,Reference);
	}

	//1.不能有连续的红色节点
	//2.每条路径的黑色节点要数量相同
	bool _Check(const Node* root,int black_num,int& Reference)
	{
		if (root == nullptr)
		{
			if (Reference == -1)//由第一次走完的路径作为参考值
			{
				Reference = black_num;
			}
			else if (Reference != black_num)//当存在黑色节点数量与参考值不同的路径时,说明违反规则
			{
				return false;
			}
			return true;
		}
		//当如果节点为红,则检查父母节点是否也为红,若是红则违反规则
		if (root->_col == RED && root->_parent && root->_parent->_col == RED)
		{
			return false;
		}
		//统计每条路径的黑色节点,当走到空时则说明该路径走完
		if (root->_col == BLACK)
		{
			black_num++;
		}

		return _Check(root->_left,black_num,Reference) && _Check(root->_right,black_num,Reference);
	}

};

总结

本章模拟实现了红黑树的核心部分,提供了测试接口,下一篇将会把红黑树进行一些改造,并且完整红黑树的部分基本接口,用于封装set和map,并且将模拟实现对set和map的封装

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

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

相关文章

远勤山丨于无声处听惊雷——写在远航汽车投产下线之际

“只要未来有前途的产业必然竞争激烈。新能源汽车目前还有很多新进入者&#xff0c;证明还有很大的成长空间。对于远航汽车&#xff0c;我们非常有信心坐上最后的牌桌。” 2023年11月8日&#xff0c;在山西运城大运新能源生产基地举办的“远航汽车下线仪式”上&#xff0c;大运…

举报“将我的电脑控作己用者”!

既然“麻辣800727”都说是“街子电信”干的&#xff0c;那么&#xff0c;我现在就正式举报&#xff1a;请依法管理宽带网&#xff0c;你国营的也不可以随意侵犯用户的人权&#xff0c;更不可以将自己变成法外之地&#xff01; 请公开答复&#xff0c;并改正&#xff0c;否则把…

Web实验(总)

目录 网站需求&#xff1a; 思路&#xff1a; 实验步骤&#xff1a; 第一步&#xff1a;准备工作 第二步&#xff1a;新建一个存储网页的目录 第三步&#xff1a;修改本地hosts映射 第四步&#xff1a;修改配置文件&#xff0c;建立基于http服务的网站 1)创建用户song和…

都快2024年了,别只使用React,需要学习一下Vue,不然没出路了

最近&#xff0c;我的朋友因为不熟悉 Vue.js 而未能通过面试。 她平时工作中大部分时间都在使用React&#xff0c;所以也懒得去了解其他前端框架。 世界上所有的前端框架我们都应该熟悉吗&#xff1f; 不&#xff0c;这是极其不合理的。 但为了生存&#xff0c;朋友还是要学…

Vue事件绑定

目录 前言 Vue事件处理 指令的语法格式 事件绑定指令—v-on 回调函数 前言 我们知道如何在原生js中实现事件绑定&#xff0c;那在vue中如何实现呢&#xff1f; Vue事件处理 指令的语法格式 <标签 v-指令名&#xff1a;参数名"表达式">{{插值语法}}</…

2D 3D 工业组态技术 meta2d JavaScript

本心、输入输出、结果 文章目录 2D 3D 工业组态技术 meta2d JavaScript前言2D 3D 工业组态技术 meta2d JavaScript 简介2D 3D 工业组态技术 meta2d JavaScript 特性丰富的组态能力0代码数据通信组态的应用多端适配能力强大的扩展能力追求卓越性能丰富的组件库资源广泛的应用场景…

React Virtual DOM及Diff算法

JSX到底是什么 使用React就一定会写JSX&#xff0c;JSX到底是什么呢&#xff1f;它是一种JavaScript语法的扩展&#xff0c;React使用它来描述用户界面长成什么样子&#xff0c;虽然它看起来非常像HTML&#xff0c;但他确实是javaScript&#xff0c;在React代码执行之前&#…

介绍几种Go语言开发的IDE

文章目录 1.前言2.几种ide2.1 Goland2.2 VsCode示例 2.3 LiteIDE2.4 Eclipse插件GoClipse2.5 Atom2.6 Vim2.7 Sublime Text 3.总结写在最后 1.前言 Go语言作为一种新兴的编程语言&#xff0c;近年来受到了越来越多的关注。 它以其简洁、高效和并发性能而闻名&#xff0c;被广…

基于STC12C5A60S2系列1T 8051单片机的数模芯片DAC0832实现数模转换应用

基于STC12C5A60S2系列1T 8051单片机定时器/计数器应用 STC12C5A60S2系列1T 8051单片机管脚图STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式及配置STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式介绍数模芯片DAC0832介绍通过按键调节数模芯片DAC0832输出模拟量控制…

ThreadLocal原理及使用场景

ThreadLocal意为线程本地变量&#xff0c;用于解决多线程并发时访问共享变量的问题 明显&#xff0c;在多线程的场景下&#xff0c;当有多个线程对共享变量进行修改的时候&#xff0c;就会出现线程安全问题&#xff0c;即数据不一致问题。常用的解决方法是对访问共享变量的代码…

dll文件【C#】

加载方法&#xff1a; [DllImport("controlcan.dll")] public static extern UInt32 VCI_OpenDevice(UInt32 DeviceType, UInt32 DeviceInd, UInt32 Reserved); 文件存放位置&#xff1a; 一般放Debug文件夹下。 运行错误&#xff1a; 原因是CPU位数选择不对&…

F8652X 984865265 F8652E 984865264

F8652X 984865265 F8652E 984865264 亚历克能够满足最严格的建筑规范开箱-不需要大量的定制&#xff0c;设计时间或劳动力&#xff0c;或专门的布线系统。 模块化ALEC系统包括三个简单的硬件组件——区域控制器(ZC001)、标准按钮墙板和物联网(IoT)网关。该系统可以无限扩展&…

Python高级语法----高级Python函数式编程

文章目录 1. 函数式编程概念2. 使用functools模块3. 深入理解lambda, map, filter, reduce函数a. lambda 函数b. map 函数c. filter 函数d. reduce 函数1. 函数式编程概念 函数式编程是一种编程范式,它将计算视为函数的评估,并避免使用程序状态及可变数据。在Python中,函数…

【VS Code插件开发】创建终端(八)

🐱 个人主页:不叫猫先生,公众号:前端舵手 🙋‍♂️ 作者简介:前端领域优质作者、阿里云专家博主,共同学习共同进步,一起加油呀! ✨优质专栏:VS Code插件开发极速入门 📢 资料领取:前端进阶资料可以找我免费领取 目录 一、createTerminal创建终端二、终端方法1、…

警方打击了大规模网络钓鱼提供商BulletProftLink

导语 最近&#xff0c;马来西亚皇家警察宣布成功打击了一个名为BulletProftLink的大规模网络钓鱼提供商。这个提供超过300个钓鱼模板的平台被查封&#xff0c;给全球网络安全带来了巨大的利好消息。本文将带您了解这个引人注目的行动背后的故事&#xff0c;并揭示BulletProftLi…

根据特定规则生成合并数据,遍历循环时会存在数据错乱等情况的问题排查(深拷贝 仅自己记录)

问题前景 在合并生成领料单&#xff0c;选择相同物料&#xff0c;合并领料&#xff0c;但因批次&#xff0c;数量不一样&#xff0c;需要单独生成一个主单据下面显示具体的物料&#xff0c;主单据的数量必须是选择单据的数量累加 错误代码演示 const temp JSON.parse(JSON…

YOLOV5中parser参数配置

源码下载链接&#xff1a;ultralytics/yolov5: YOLOv5 &#x1f680; in PyTorch > ONNX > CoreML > TFLite (github.com) 需要配置的参数&#xff1a;--data parser.add_argument(--data, ...)&#xff1a;添加一个用于数据配置文件的路径的参数。 可以直接修改&am…

达索系统SOLIDWORKS 2024钣金和结构系统新功能

达索系统SOLIDWORKS钣金和结构系统是大家比较熟悉的模块了&#xff0c;在2024版本中钣金和结构系统功能也做了很棒的提升。接下来让我们看看如何使用达索系统SOLIDWORKS 2024钣金和结构系统的一些新功能快速完成相应的设计。 达索系统SOLIDWORKS 2024的钣金提供了槽口延伸功能…

【C++】一维字符数组 与 二维字符数组

一维字符数组 一维字符数组 可以通过数组名直接进行整体输入和输出&#xff08;注意&#xff1a;当使用一维字符数组存储字符串时&#xff0c;因为元素尾部会有一个空字符\0,所以需要给空字符\0留一个位置&#xff09; char a[5]; cin>>a; cout<<a;二维字符数组 …

通过ping来测试网络带宽

在windows下通过 ping -l 5000 192.168.31.1 其中5000为设置ping的单个包的大小 192.168.31.1为目的地地址 假设ping 5000字节平均耗费1ms而ping 45000字节平均耗费4ms&#xff0c;因此&#xff0c;往返额外花费3ms单程额外花费1.5ms&#xff0c;多发送40000字节即320000比特…