【C++】红黑树的插入实现

news2025/1/13 7:47:58

目录

  • 红黑树的概念
    • 红黑树的性质
  • 红黑树节点的定义
  • 红黑树的插入操作
    • 当p(父节点)在g(祖父节点)左子树`grandfather->_left == parent`
    • 当p(父节点)在g(祖父节点)右子树`grandfather->_right == parent`

📖 前言
本篇文章中红黑树的插入用到左单旋和右单旋在AVL树的插入中已经有做了很详细的介绍了,如果有不清楚可以在熟悉熟悉,链接如下。如果对左单旋和右单旋都很熟悉的就可以阅读本篇文章了。

【C++】AVL树的插入实现

红黑树的概念

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

红黑树的性质

  1. 每个结点不是红色就是黑色
  2. 根节点是黑色的
  3. 如果一个节点是红色的,则它的两个孩子结点是黑色的 (不能出现连续的红色结点,但是可以出现连续的黑色结点)。
  4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点 (每条路径上都有相同数目的黑色结点,这里的路径是空结点到根结点)。
  5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)

🔍为什么满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点个数的两倍?
根据上面的性质可以做出以下结论
最短路径是:全黑
最长路径是:一黑一红

红黑树节点的定义

💻代码演示

enum Colour
{
	RED,
	BLACK,
};

template <class K, class V>
struct RBTreeNode
{
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	pair<K, V> _kv;
	Colour _col;

	RBTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		, _col(RED)//如果是黑色,所以路径都要调整,才能满足红黑树的特点。
	{}
};

template<class K, class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;
public:
	bool Insert(const pair<K, V>& kv);
private:
	Node* _root = nullptr;
};

🔍在节点的定义中,为什么要将节点的默认颜色给成红色的?
因为如果定义为黑色结点,那整个树的所有路径都需要改变,红黑树要求每条路径上都有相同数目的黑色结点。

红黑树的插入操作

💻代码演示

bool Insert(const pair<K, V>& kv)
	{
		if (nullptr == _root)//第一次插入
		{
			_root = new Node(kv);
			_root->_col = BLACK;//红黑树的根结点是黑色
			return true;
		}
		//插入的树不是空树
		Node* parent = nullptr;
		Node* cur = _root;
		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);
		if (parent->_kv.first < cur->_kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		//链接父结点
		cur->_parent = parent;
		//检测新节点插入后,红黑树的性质是否造到破坏
		return true;
	}


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

约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点

当p(父节点)在g(祖父节点)左子树grandfather->_left == parent

🔍情况一: cur为红cur == parent->left,p为红,g为黑,u存在且为红
在这里插入图片描述

🔍情况二: cur为红cur == parent->_left,p为红,g为黑,u不存在/u存在且为黑
在这里插入图片描述

🔍 情况三: cur为红cur == parent->_right,p为红,g为黑,u不存在/u存在且为黑
在这里插入图片描述

下面的和上面的情况一相似只是方向不一样。

当p(父节点)在g(祖父节点)右子树grandfather->_right == parent

🔍情况一: cur为红cur == parent-_right,p为红,g为黑,u存在且为红
在这里插入图片描述

🔍情况二: cur为红cur == parent->_right,p为红,g为黑,u不存在/u存在且为黑
在这里插入图片描述

🔍 情况三: cur为红cur == parent->_left,p为红,g为黑,u不存在/u存在且为黑
在这里插入图片描述
💻代码演示

bool Insert(const pair<K, V>& kv)
{
	if (nullptr == _root)//第一次插入
	{
		_root = new Node(kv);
		_root->_col = BLACK;//红黑树的根结点是黑色
		return true;
	}
	//插入的树不是空树
	Node* parent = nullptr;
	Node* cur = _root;
	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);
	if (parent->_kv.first < cur->_kv.first)
	{
		parent->_right = cur;
	}
	else
	{
		parent->_left = cur;
	}
	//链接父结点
	cur->_parent = parent;

	while (parent && parent->_col == RED)
	{
		Node* grandfather = parent->_parent;
		if (grandfather->_left == parent)
		{
			Node* uncle = grandfather->_right;
			//情况一: cur为红,p为红,g为黑,u存在且为红
			//            g(黑)
			//     p(红)        u(红)
			//c(红)
			//解决方式:将p,u改为黑,g改为红,然后把g当成cur,继续向上调整。
			//            g(红)
			//     p(黑)        u(黑)
			//c(红)
			if (uncle && uncle->_col == RED)
			{
				parent->_col = BLACK;
				uncle->_col = BLACK;
				grandfather->_col = RED;
				//继续向上调整
				cur = grandfather;
				parent = cur->_parent;
			}
			else
			{
			//cur为红,p为红,g为黑,u不存在/u存在且为黑
			
				if (cur == parent->_left)//cur插入在parent的左边
				{
			// u不存在
			//            g(黑)
			//     p(红)        
			//c(红)
			// 解决方式:则进行右单旋转
			//            p(黑)
			//     c(红)        g(红)
			// -------------------------------------------------
			//u存在且为黑(一定是由情况一变换而来的),p的右子树和c的左右子树一定存在,并且都存在黑结点
			//              g(黑)
			//        p(红)       u(黑) 
			//   c(红)
			// 解决方式:则进行右单旋转
			//            p(黑)
			//     c(红)        g(红)
			//                        u(黑)
					RotateR(grandfather);
					grandfather->_col = RED;
					parent->_col = BLACK;
					//cur->_col = RED;
				}
				else//cur插入在parent的右边
				{
			// u不存在
			//            g(黑)
			//     p(红)        
			//         c(红)
			//解决方式:p点进行左单选,g点进行右单选
			//左单选       g(黑)
			//     c(红)        
			//p(红)
			//右单选       
			//     c(黑)        
			//p(红)      g(红)
			//---------------------------------------------------------------------
			// u存在
			//              g(黑)
			//     p(红)            u(黑) 
			//          c(红)
			//解决方式:p点进行左单选,g点进行右单选
			//左单选       g(黑)
			//     c(红)           u(黑)
			//p(红)
			//右单选       
			//     c(黑)        
			//p(红)      g(红)
			//		          u(黑)
					RotateL(parent);
					RotateR(grandfather);
					grandfather->_col = RED;
					//parent->_col = RED;
					cur->_col = BLACK;
				}
				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 = cur->_parent;
			}
			else
			{
				if (cur == parent->_right)
				{
					RotateL(grandfather);//RotateR
					grandfather->_col = RED;
					parent->_col = BLACK;
					//cur->_col = RED;
				}
				else
				{

					RotateR(parent);
					RotateL(grandfather);
					grandfather->_col = RED;
					//parent->_col = RED;
					cur->_col = BLACK;
				}

				break;
			}
		}
	}

	_root->_col = BLACK;
	return true;
}

//右子树高,进行左单旋
void RotateL(Node* parent)
{
	Node* childR = parent->_right;
	Node* childRL = childR->_left;

	parent->_right = childRL;
	if (childRL)//如果childRL不为空,要链接父节点
		childRL->_parent = parent;

	Node* pparent = parent->_parent;
	childR->_left = parent;
	parent->_parent = childR;

	if (nullptr == pparent)//说明是根结点
	{
		_root = childR;
		//_root->_parent = nullptr;
		childR->_parent = nullptr;
	}
	else
	{
		if (pparent->_left == parent)
		{
			pparent->_left = childR;
		}
		else
		{
			pparent->_right = childR;
		}

		childR->_parent = pparent;//链接父结点
	}
}
//左子树高,进行右单旋
void RotateR(Node* parent)
{
	Node* childL = parent->_left;
	Node* childLR = childL->_right;

	parent->_left = childLR;
	if (childLR)
		childLR->_parent = parent;

	Node* pparent = parent->_parent;
	childL->_right = parent;
	parent->_parent = childL;

	if (_root == parent)//说明是根结点
	{
		_root = childL;
		_root->_parent = nullptr;
	}
	else
	{
		if (pparent->_left == parent)
		{
			pparent->_left = childL;
		}
		else
		{
			pparent->_right = childL;
		}

		childL->_parent = pparent;//链接父结点
	}
}

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

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

相关文章

【Python】python进阶篇之面向对象编程

面向对象编程 封装、继承、多态 封装&#xff1a;提高程序安全性 将数据&#xff08;属性&#xff09;和行为&#xff08;方法&#xff09;包装到类中。在方法内部对属性进行操作&#xff0c;在类的外部调用方法。无需关心方法内部的具体实现细节&#xff0c;从而隔离代码复杂…

生成器模式(Builder)

定义 生成器是一种创建型设计模式&#xff0c;使你能够分步骤创建复杂对象。该模式允许你使用相同的创建 代码生成不同类型和形式的对象。 前言 1. 问题 假设有这样一个复杂对象&#xff0c;在对其进行构造时需要对诸多成员变量和嵌套对象进行繁复的初始化工作。这些初始化…

体验Vue神奇的响应式原理:让你的应用更快、更流畅

文章目录 I. 引言介绍Vue.js的响应式原理及其重要性概述本文的内容 II. 数据劫持解释什么是数据劫持Vue如何实现数据劫持示例说明 II. 依赖收集解释什么是依赖收集Vue如何实现依赖收集示例说明 IV. 派发更新解释什么是派发更新Vue如何实现派发更新示例说明 V. 响应式原理运作流…

数据仓库建设指导说明

文章目录 1、概念2、数仓特点3、数仓架构3.1、数据集市3.2、Inmon 架构3.3、Kimball 架构3.3.1、表分区3.3.1.1、事实表3.3.1.2、维度表3.3.1.2.1、维表设计步骤3.3.1.2.2、维度设计的建议3.3.1.2.3、主键设计3.3.1.2.4、缓慢变化维 SCD3.3.1.2.5、维表的整合与拆分3.3.1.2.5.1…

Verdi 之配置及波形打开

目录 写在前边 1.verdi的配置 2. 波形的产生及打开 写在前边 本部分内容主要对Verdi的学习进行总结&#xff0c;大概分三篇文章进行叙述。 1.verdi的配置 1.首先打开.bashrc文件进行环境配置 2.Verdi 配置如下&#xff1a; verdi_HOME: 配置Verdi的home目录&#xff0…

如何制作数字人的模型

首先我们先来了解一下什么是数字人&#xff0c;根据 中国人工智能产业发展联盟发布的《2020年虚拟数字人发展白皮书》指出&#xff0c;数字人意 指具有数字化外形的虚拟人物&#xff0c;除了拥有人的外观、人的行为之外&#xff0c;还拥有人的思想&#xff0c;具有识别外界环境…

【深入浅出RocketMQ原理及实战】「底层原理挖掘系列」透彻剖析贯穿RocketMQ的消息顺序消费和并发消费机制体系的原理分析

透彻剖析贯穿RocketMQ的消息顺序消费和并发消费机制体系的原理分析 DefaultMQPushConsumerImpl拉取消息consumeMessageService的并发消费和顺序消费并发消费顺序消费concurrently 创建 ConsumeRequestconcurrently ConsumeRequest#run 消费主体逻辑消费结束之后清除数据 orderl…

黑马程序员前端 Vue3 小兔鲜电商项目——(十)订单页

文章目录 路由配置和基础数据渲染模板代码配置路由封装接口渲染数据 切换地址-打开弹框交互切换地址-地址切换交互生成订单支付页组件封装订单接口绑定事件 路由配置和基础数据渲染 模板代码 新建 src\views\Checkout\index.vue 文件&#xff0c;添加以下代码&#xff1a; &…

容器管理中关于CGroup的那些事

前言 在一个docker宿主机上可以启动多个容器&#xff0c;默认情况下&#xff0c;docker并没有限制其中运行的容器使用硬件资源。 但如果在实际环境中&#xff0c;容器的负载过高&#xff0c;会占用宿主机大量的资源。这里的资源主要指的CPU&#xff0c;内存&#xff0c;和IO带…

Python Pandas 筛选数据以及字符串替换

str.replace使用示例 假设有一个DataFrame df&#xff0c;其中有一个列名为text&#xff0c;包含一些文本字符串&#xff1a; import pandas as pd data {text: [hello world, foo bar, hello there]} df pd.DataFrame(data) 我们可以使用str.replace方法来替换字符串。比…

操作系统——Linux 进程控制

一、实验题目 Linux 进程控制 二、实验目的 通过进程的创建、撤销和运行加深对进程概念和进程并发执行的理解&#xff0c;明确进程和程序之间的区别。 三、实验内容&#xff08;实验原理/运用的理论知识、算法/程序流程图、步骤和方法、关键代码&#xff09; &#xff08;…

开源网安S-SDLC解决方案,为银行打造主动防御的安全体系

​某银行是全国上市最早的一批股份制商业银行&#xff0c;总部位于深圳&#xff0c;在全国拥有上百家分行、上千家营业机构&#xff0c;资产总额达数千亿元。近年来&#xff0c;该银行围绕数据化、智能化、生态化&#xff0c;全力打造“数字银行”&#xff0c;助力建设“数字中…

第十六届CISCN复现----MISC

1.被加密的生产流量 下载附件&#xff0c;发现是一个文件名为modus的压缩包&#xff0c;解压后是一个pcap文件&#xff0c;用wireshark打开 文件名modus&#xff0c;已经提示了工控流量&#xff0c;很多情况下都是和TCP协议结合起来的 工控CTF之协议分析1——Modbus_ctf modb…

基于java+swing+mysql学生信息管理系统V2.0

基于javaswingmysql学生信息管理系统V2.0 一、系统介绍二、功能展示1.项目骨架2.数据库表3.项目内容4.登陆5.学生信息查询6、学生信息添加7、学生信息修改8、学生信息删除 四、其它1.其他系统实现五.获取源码 一、系统介绍 项目类型&#xff1a;Java SE项目&#xff08;awtswi…

Gorm Many To Many

写cmdb的时候要去做一些软件资源的落库&#xff0c;发布要使用到的应用属性。应用有哪些属性&#xff1f; 应用有它的type类型&#xff0c;是api还是admin&#xff0c;还是job或者task。它的语言是go java.....&#xff0c;它的own也就是属于哪个开发的&#xff0c;这是它的属…

设备管理模块实现

文章目录 1 .导航树模块的实现2. 查询定位功能的实现3. 资源管理功能的实现4. 电缆段入沟功能实现 1 .导航树模块的实现 导航树的各节点是通过Ajax 技术异步加载的&#xff0c;系统初始化时导航树只会加载初始的城市节点&#xff0c;用户根据自身需要选择相应的父节点加载其逻…

Flink安装与编程实践

系列文章目录 Ubuntu常见基本问题 Hadoop3.1.3安装&#xff08;单机、伪分布&#xff09; Hadoop集群搭建 HBase2.2.2安装&#xff08;单机、伪分布&#xff09; Zookeeper集群搭建 HBase集群搭建 Spark安装和编程实践&#xff08;Spark2.4.0&#xff09; Spark集群搭建 文章目…

mongoDB相关知识

目录 常用操作删除数据库 启动问题集如何远程访问mongDB数据库由于widows安全策略&#xff0c;linux访问不到windows的mongDB 常用操作 删除数据库 windows下mongDB通过下面命令行进入 D:\mongodb\mongodb-win32-x86_64-2008plus-ssl-3.6.23-8-gc2609ed3ed\bin>mongod.exe…

Unity开发前的一些建议1_设置脚本的编码格式,设置IDE的编码格式

Unity开发前的一些建议1_设置脚本的编码格式&#xff0c;设置IDE的编码格式 乱码之后是是不可以撤回的哦。 这么做的理由&#xff0c;Unity右侧的Inspector面板看代码是UTF-8格式的。可以在Inspector中速览代码&#xff0c;且如果修改IDE&#xff0c;UTF-8比其他编码格式用的…

K8S复习

本文原文出自本人自己复习时整理&#xff0c;原文非常系统&#xff0c;建议拜师#yyds干货盘点# 手把手教你玩转 Kubernete 集群搭建(03)_wzlinux的博客-CSDN博客 1.docker的优势 在某一段时期内&#xff0c;大家一提到 Docker&#xff0c;就和容器等价起来&#xff0c;认为 Doc…