C++红黑树插入操作的模拟实现

news2024/10/28 12:54:35

1.红黑树概念

1.1什么是红黑树

红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或 Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。mapset的底层就是以红黑树为框架,因为红黑树与AVL相比在插入和删除时更有优势。

如下图即为一棵红黑树,通过继续对红黑树性质的学习,我们将红黑树如何实现接近平衡

1.2.红黑树的性质

1. 每个结点不是红色就是黑色

2. 节点是黑色的 

3. 如果一个节点是红色的,则它的两个孩子结点是黑色的 (即不能出现连续的红色节点

4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点每条路径的黑色节点数目相等

为什么满足上面的性质,红黑树就能确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的?

由图推广,最长路径一定是全黑设最短路径长度为h,最长路径一定为一黑一红交错排列。

则:最短路径 * 2 >= 最长路径

最长路径 <= 2h

这只是符合规则的理论最短和最长,实践中最短和最长不一定存在。

2.红黑树的结构及插入

2.1红黑树的结构

红黑树通过维护规则保持树的相对平衡,当插入节点破坏规则时,将进行变色旋转操作。在进行变色或旋转操作时,需要找到插入点的父节点。为方便操作这里将红黑树的节点设计为三叉链的结构——分别指向左孩子、右孩子、父亲节点。

节点的定义如下

//节点的颜色
enum Colour
{
	RED,
	BLACK
};


//模版参数Key和Value
template<class K, class V>
struct RBTreeNode
{
	RBTreeNode(const pair<K, V>& kv)
		: _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr)
		, _kv(kv), _colour(RED)//默认每个节点颜色为红色
	{}
	RBTreeNode<K, V>* _pLeft;// 该节点的左孩子
	RBTreeNode<K, V>* _pRight; // 该节点的右孩子
	RBTreeNode<K, V>* _pParent; // 该节点的双亲
	pair<K, V> _kv; // 该节点储存的数据  
	Colour _colour;// 该节点的颜色

};

树的定义如下

 
template<class K, class V>
class RBTree {
	typedef RBTreeNode<K, V> Node;
	Node* _root;

public:
	RBTree() = default;
	~RBTree()
	{
		Destroy(_root);
		_root = nullptr;
	}
private:
	void Destroy(Node* root)
	{
		if (root == nullptr)
			return;

		Destroy(root->_pLeft);
		Destroy(root->_pRight);
		delete root;
	}

};

2.2红黑树的插入操作

 我们先来讨论插入的节点应该定义为什么颜色

1.定义为黑色,除非为整棵树的根,否则插入一个黑色节点必定使根到这个节点路径的黑色节点数量+1,则必定违反红黑树性质4——每个结点到其所有后代叶结点的简单路径上,不包含相同数目的黑色结点。

2.定义为红色,插入一个红色节点,若其父节点是红色的则会违反红黑树性质2,出现了连续的红色节点。

然而综合两种情况,插入黑色节点时必定违反规则(除根外),插入红色节点时可能违反规则,我们优先选择风险更小的那种插入方式将插入节点默认为红色,然后在违反规则时特殊处理

红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步:

1. 按照二叉搜索树的规则插入新节点

2.检测新节点插入后,红黑树的性质是否遭到破坏

   因为新节点的默认颜色是红色,因此:如果其双亲节点的颜色是黑色,没有违反红黑树任何性质,则不需要调整;但当新插入节点的双亲节点颜色为红色时,就违     反了性质三不能有连 在一起的红色节点,此时需要对红黑树分情况来讨论:

下文中,我们令 cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点(g:grandfather、p:parent、u:uncle)

而插入时双亲节点为红时又存在以下两种情况,需要特殊处理

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

图中三角形节点a、b、c、d、e为抽象的树,可以为空或一棵子树,但他们的存在保证符合性质4

 这种情况有以下几种具体情况

1.a、b、c、d、e都为空

2.a、b、c、d、e不为空但每条路径黑色节点数目相等

3.cur原本为黑,a、b的插入使cur变红

这样的情况,对子树的调整方法:p、u变黑,g变红,这样的调整方法因为g变红继续向祖先调整(不确定g的双亲节点的颜色)

这种情况变色的调整方法使g的黑色向g的孩子转移,没有增加每条路径黑色节点的数目

如下

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

u存在且为黑色的情况a、b、c路径至少有一个黑色节点

 这时我们用变色处理不了问题了,必须要旋转处理

而旋转也分为两种情况

二.1.纯粹的一边高的情况,如图

调整方法: 单次旋转,p变黑,g变红

这种调整会使根到cur的孩子所有路径高度降低(使较高路径变短),且维护了规则

下图为右旋(即向右旋转),左旋思想类似

二.2.一边的中间高的情况

如cur在p的右边的情况,如图

这种情况一次左旋解决不了问题,我们可以先旋转一次变成二.1.那样一边高的情况,再旋转调整

调整方法:先在p子树进行左旋,再对g右旋,cur变黑,g变红

 下面给出左右旋及插入操作的完整代码

	//红黑树的插入操作
	void Insert(const pair<K, V>& kv)
	{
		Node* newnode = new Node(kv);
		Node* cur = _root;
		Node* parent = nullptr;

		//为空树,若为根节点,颜色为黑色
		if (_root == nullptr)
		{
			newnode->_colour = BLACK;
			_root = newnode;
			return;
		}


		//找到插入位置
		while (cur)
		{
			if (kv.first == cur->_kv.first)
			{
				return;
			}
			else if (kv.first < cur->_kv.first)
			{
				parent = cur;
				cur = cur->_pLeft;
			}
			else if (kv.first > cur->_kv.first)
			{
				parent = cur;
				cur = cur->_pRight;
			}
		}

		//链接节点
		cur = newnode;
		cur->_pParent = parent;
		if (kv.first < parent->_kv.first)
		{
			parent->_pLeft = cur;
		}
		else
		{
			parent->_pRight = cur;
		}


		//根据规则进行调整
		//使用循环调整到根节点
		while (parent)
		{
			//若p为黑,不继续向上调整
			if (parent->_colour == BLACK)
			{
				break;
			}
			//若p为红,进行调整
			else
			{
				//p为红则必有parent,且grandpa必为黑,因为根节点为黑
				Node* grandpa = parent->_pParent;
				Node* uncle = nullptr;
				if (parent == grandpa->_pLeft)
				{
					uncle = grandpa->_pRight;
				}
				else
				{
					uncle = grandpa->_pLeft;
				}
				//情况1:p和u都为红色,将p和u变黑,g变红,并继续向上更新
				if (parent->_colour == RED && uncle != nullptr && uncle->_colour == RED)
				{
					parent->_colour = BLACK;
					uncle->_colour = BLACK;
					grandpa->_colour = RED;

					//若grandpa为根节点,将其变回黑色,退出循环
					if (grandpa == _root)
					{
						grandpa->_colour = BLACK;
						break;
					}

					//迭代进行下一轮循环
					cur = grandpa;
					parent = cur->_pParent;
				}
				//情况2:p为红,u为黑,进行旋转后再变色
				else if (parent->_colour == RED && (uncle == nullptr || uncle->_colour == BLACK))
				{
					//先判断p为g的左还是右
					if (parent == grandpa->_pLeft)
					{
						//2.1 单旋的情况,p为g的左孩子,cur为p的左孩子,右单旋
						if (cur == parent->_pLeft)
						{
							RotatoR(grandpa);

							//颜色调整
							parent->_colour = BLACK;
							grandpa->_colour = RED;
						}
						//2.2 双旋的情况,p为g的左孩子,cur为p的右孩子,先p左单旋,再g右单旋
						else
						{
							RotatoL(parent);
							RotatoR(grandpa);

							//颜色调整
							cur->_colour = BLACK;
							grandpa->_colour = RED;
						}
					}
					else
					{
						//2.1 单旋的情况,p为g的右孩子,cur为p的右孩子,左单旋
						if (cur == parent->_pRight)
						{
							RotatoL(grandpa);

							//颜色调整
							parent->_colour = BLACK;
							grandpa->_colour = RED;
						}
						//2.2 双旋的情况,p为g的右孩子,cur为p的左孩子,先p右单旋,再g左单旋
						else
						{
							RotatoR(parent);
							RotatoL(grandpa);

							//颜色调整
							cur->_colour = BLACK;
							grandpa->_colour = RED;
						}
					}

					//子树根节点为黑,不会影响祖先,退出循环
					break;

				}
			}
		}
	}


	//左单旋
	//可以使根到cur的孩子所有路径高度降低,且维护了规则
	void RotatoL(Node* parent)
	{
		Node* subR = parent->_pRight;
		Node* subRL = subR->_pLeft;
		Node* parentParent = parent->_pParent;

		//先链接节点
		if (subRL != nullptr)
			subRL->_pParent = parent;
		parent->_pRight = subRL;

		parent->_pParent = subR;
		subR->_pLeft = parent;

		subR->_pParent = parentParent;
		if (parentParent == nullptr)
		{
			//若parentParent为空则原Parent为整个树的根
			_root = subR;
		}
		else
		{
			if (parent == parentParent->_pLeft)
			{
				parentParent->_pLeft = subR;
			}
			else if (parent == parentParent->_pRight)
			{
				parentParent->_pRight = subR;
			}
		}



	}



	//右单旋,与左旋思想类似
	void RotatoR(Node* parent)
	{
		Node* subL = parent->_pLeft;
		Node* subLR = subL->_pRight;
		Node* parentParent = parent->_pParent;

		if (subLR != nullptr)
			subLR->_pParent = parent;
		parent->_pLeft = subLR;

		parent->_pParent = subL;
		subL->_pRight = parent;

		subL->_pParent = parentParent;
		if (parentParent == nullptr)
		{
			_root = subL;

		}
		else
		{
			if (parent == parentParent->_pLeft)
			{
				parentParent->_pLeft = subL;
			}
			else if (parent == parentParent->_pRight)
			{
				parentParent->_pRight = subL;
			}
		}


	}

                

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

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

相关文章

Linux 重启命令全解析:深入理解与应用指南

Linux 重启命令全解析&#xff1a;深入理解与应用指南 在 Linux 系统中&#xff0c;掌握正确的重启命令是确保系统稳定运行和进行必要维护的关键技能。本文将深入解析 Linux 中常见的重启命令&#xff0c;包括功能、用法、适用场景及注意事项。 一、reboot 命令 功能简介 re…

Flutter图片控件(七)

1、加载图片 import package:flutter/material.dart;void main() {runApp(const MaterialApp(home: MyHomePage(),)); }class MyHomePage extends StatelessWidget {const MyHomePage({super.key});overrideWidget build(BuildContext context) {return Scaffold(appBar: AppB…

Python:背景知识及环境安装

一、计算机的基础概念 1.1 什么是计算机&#xff1f; 最早我们有计算器&#xff0c;但是他只能完成算数运算的功能 而计算机能完成的工作有&#xff1a; &#xff08;1&#xff09;算术运算 &#xff08;2&#xff09;逻辑判断 &#xff08;3&#xff09;数据存储 &#xff08…

k8s 二进制部署安装(一)

目录 环境准备 初始化操作系统 部署docker 引擎 部署 etcd 集群 准备签发证书环境 部署 Master01 服务器相关组件 apiserver scheduler controller-manager.sh admin etcd 存储了 Kubernetes 集群的所有配置数据和状态信息&#xff0c;包括资源对象、集群配置、元数据…

基于SSM+小程序的旅游社交登录管理系统(旅游4)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1、项目介绍 ​ 本旅游社交小程序功能有管理员和用户。管理员有个人中心&#xff0c;用户管理&#xff0c;每日签到管理&#xff0c;景点推荐管理&#xff0c;景点分类管理&#xff0c;防疫查询管理&a…

51单片机完全学习——DS18B20温度传感器

一、DS18B20数据手册解读 首先我们知道DS18B20使用的是单总线传输&#xff0c;默认情况下读出来的温度是12位的&#xff0c;我们这里只讨论外部电源供电这种情况。 有这张图片我们知道&#xff0c;12位温度的最小分辨率是10^-4次方&#xff0c;因此就是0.0625.我们只需要将最后…

论文阅读(二十三):Squeeze-and-Excitation Networks

文章目录 1.介绍2.原理3.代码4.SE模块的应用 论文&#xff1a;Squeeze-and-Excitation Networks   论文链接&#xff1a;Squeeze-and-Excitation Networks   代码链接&#xff1a;Github 1.介绍 卷积算子使网络能够在每一层的局部感受野中融合空间&#xff08;spatial&…

内容安全与系统构建加速,助力解决生成式AI时代的双重挑战

内容安全与系统构建加速&#xff0c;助力解决生成式AI时代的双重挑战 0. 前言1. PRCV 20241.1 大会简介1.2 生成式 Al 时代的内容安全与系统构建加速 2. 生成式 AI2.1 生成模型2.2 生成模型与判别模型的区别2.3 生成模型的发展 3. GAI 内容安全3.1 GAI 时代内容安全挑战3.2 图像…

Linux 进程间通信_匿名管道

1.程间通信目的 : 数据传输&#xff1a;一个进程需要将它的数据发送给另一个进程 资源共享&#xff1a;多个进程之间共享同样的资源。 通知事件&#xff1a;一个进程需要向另一个或一组进程发送消息&#xff0c;通知它&#xff08;它们&#xff09;发生了某种事件&#xff08;如…

Linux CentOS7下创建SFTP服务器

本文详细介绍了在Linux CentOS上部署安全文件传输协议&#xff08;SFTP&#xff09;服务器的全过程。SFTP基于SSH&#xff08;安全壳层协议&#xff09;提供文件传输服务&#xff0c;继承了SSH的安全特性&#xff0c;如数据加密、完整性验证和服务器认证等&#xff0c;确保数据…

信号与系统学习:周期信号的频谱

一、概念 1. 什么是频谱&#xff1f; 频谱描述了信号在不同频率上的能量分布对于一个周期信号&#xff0c;其频谱通常是离散的&#xff0c;由一系列离散的频率成分组成 2. 周期信号与傅里叶级数 周期信号可以用傅里叶级数展开&#xff0c;表示为无数个正弦和余弦&#xff0…

巡飞单机多旋翼无人机技术详解

巡飞单机多旋翼无人机技术是一种集成了多种先进技术的无人机系统&#xff0c;它具备自主飞行、长续航、高精度控制以及多任务负载能力等特点。以下是对巡飞单机多旋翼无人机技术的详细解析&#xff1a; 一、机架与结构设计 1.材料选择&#xff1a;为了确保无人机能够承载足够…

基于深度学习的图像修复系统设计与实现(PyQt5、CodeFormer ffhq-dataset数据集)

&#x1f497;博主介绍&#x1f497;&#xff1a;✌在职Java研发工程师、专注于程序设计、源码分享、技术交流、专注于Java技术领域和毕业设计✌ 温馨提示&#xff1a;文末有 CSDN 平台官方提供的老师 Wechat / QQ 名片 :) Java精品实战案例《700套》 2025最新毕业设计选题推荐…

logback日志导入使用

1导入配置 <!-- 日志 &#xff0c; 会自动传递slf4j门面--> <dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.3</version> </dependency>2 引入配置 Logback要求…

Synergy遇见的问题

1.两台设备无法ping通 首先两个设备是在同一个局域网中&#xff0c;但任然是无法ping通 问题所在&#xff1a;防火墙进行了隔离&#xff1b; 解决方法&#xff1a; &#xff08;1&#xff09;关闭防火墙 没有用过&#xff0c;个人感觉不怎么安全就没有使用&#xff1b; &am…

react18中的受控与非受控组件及ref的使用

受控与非受控组件 受控组件,基于修改 state 的值,修改组件内部的状态&#xff0c;来实现页面的更新&#xff0c;推荐使用 非受控组件&#xff0c;基于 ref 获取 dom 的值&#xff0c;来实现页面的更新,不推荐使用,偶尔特殊的场景会使用 给需要获取的元素设置 ref“xxx”,后期基…

一步一步从微信小程序获取asp.net Core API的数据

前面我们说过&#xff0c;如何使用微信小程序获取asp.net的数据&#xff0c;这里我们继续介绍如何获取asp.net core api的数据。两者之间还是有一些差别的。本篇博文旨在详细介绍如何一步一步从微信小程序获取asp.net Core API的数据。 文章目录 一、建立并了解asp.net core we…

Git 创建SSH秘钥

1、命令行输入 ssh-keygen -t rsa -b 4096 2、系统提示你“Enter a file in which to save the key”&#xff0c;直接按回车键 3、再提示你输入密码的时候直接按回车键&#xff0c;创建没有密码的SSH密钥 4、密钥对创建后&#xff0c;可以在自己电脑对应的 ~/.ssh 目录下找到…

vue 果蔬识别系统百度AI识别vue+springboot java开发、elementui+ echarts+ vant开发

编号&#xff1a;R03-果蔬识别系统 简介&#xff1a;vuespringboot百度AI实现的果蔬识别系统 版本&#xff1a;2025版 视频介绍&#xff1a; vuespringboot百度AI实现的果蔬识别系统前后端java开发&#xff0c;百度识别&#xff0c;带H5移动端&#xff0c;mysql数据库可视化 1 …

Unity编辑器制作多级下拉菜单

Unity编辑器下拉菜单 大家好&#xff0c;我是阿赵。   在Unity引擎里面编写工具插件&#xff0c;有时候会用到一些特殊的菜单形式&#xff0c;比如下拉选项。 通过下拉菜单&#xff0c;给用户选择不同的选项。   如果只是一层的下拉列表&#xff0c;可以用EditorGUILayout.…