C++ - RBTree

news2025/1/12 13:22:14

前面的文章中我们讲述了以二叉搜索树为基础的AVL树,本文中我们将继续讲一种二叉搜索树为基础的红黑树。

红黑树的概念

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

红黑树的性质

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

通过上述的性质就可以满足,红黑树确保没有一条路径会比其他路径长出俩倍这个限制。红黑树的最短路径为全黑,最长路径为一黑一红, 红黑相间。NIL结点就是空节点,我们上面图中的树一共有11条路径,计算黑色节点的数量需要计算到空节点,这样才能够对红黑树形成稳定的控制。

假设全部的黑色结点有N个,最短的路径就是logN,整棵树的结点数量就在N到2N之间,最长的路径为2longN,红黑树与AVL树进行比较如果是十亿个数据,AVL树的话大概需要查找30次,而红黑树大概需要60次,相对于CPU来说差距并不大,而AVL树需要的是严格的平衡,但是红黑树并不需要非常的严格,例如下面的就是一颗红黑树它不需要处理,但如果变为了AVL树就需要进行旋转。

红黑树结点的定义

// 结点的颜色
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) // 新插入的节点如果时黑色一定会违反规则四,如果插入的是红色,有可能会违反规则三,因此在这里选择惩罚概率较小的规则三,选择新增插入的结点为红色
	{}
};

红黑树的插入操作

因为红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树首先按照二叉搜索树的规则进行插入。然后检测新节点插入之后是否红黑树的性质会造成破坏。

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

在插入是需要注意的是根节点一定是黑色的,那么新插入的节点的颜色该如何去定义,假设我们插入的新结点是黑色的,那么就一定会会违反红黑树性质的第四条,假如我们插入的新节点是红色的,此时就会分为不同的情况:如果父节点是黑色的就可以正常的进行插入过工作,如果父节点是红色的就会违反红黑树性质的第三条。因此综合上述的情况就可以得到,我们新插入的节点应该为红色的节点。

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

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

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

首先我们以上面的红黑树为例子进行分析:

在上图的位置进行插入操作,通过将父亲节点和叔叔节点变黑,将祖父节点变红即可完成一次修正,但是此时会发现祖父节点与其父节点又有连续两个红色结点的冲突,但是这个冲突与之前的插入相同,因此重复之前的操作即可,最后如果祖父结点已经是根节点了,就将根节点修正为黑色。

还有一个例子:就适当叔叔节点为空时即在6号结点的子树出插入新节点:

此时,由于有两个连续的红色结点的存在,可能会使最长路径大于两倍的最小路径的出现,那么就需要记性旋转操作将树的高度变低。上图演示的就是左单旋+变色处理。

下面我们来看一下抽象图:

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

这种情况下的抽象图如下所示:

将其具象进行分析可得下面的情况:

左边的树即为a、b、c、d、e都为空的情况,右边即为c、d、e是子树的情况。 

这里的c、d、e在第二种具象的情况下只要有下面四种情况的任意一种有一个黑色的结点即可。

这种情况的解决方式就是: 将p,u改为黑,g改为红,然后把g当成cur,继续向上调整。

还需要注意的就是,除了从情况一可能变为情况一,从情况一还有可能会变为其他的情况,例如:

这就是从一种情况变为了另一种情况因此这需要不同的处理方法。 

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

抽象图有如下的两种情况:

cur原来是黑色的上图的右边的情况即为从情况一变过来的,c是有一盒黑色节点的子树,d、e是空或者有一个红色的结点。

解决方式:p为g的左孩子,cur为p的左孩子,则进行右单旋转;相反,p为g的右孩子,cur为p的右孩子,则进行左单旋转;p、g变色--p变黑,g变红。

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

情况三与情况二是类似的不过在情况二中需要的是单旋,在情况三中需要进行的是双旋的操作。

上图中可以看出,第三种情况进行旋转之后就变为了第二种情况,进行一次单旋即可完成处理。 

代码实现:

bool Insert(const pair<K, V>& kv) {
	if (_root == nullptr) {
		_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 > kv.first) {
		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;
			// 情况1:u存在且为红,变色处理,并继续往上处理
			if (uncle && uncle->_col == RED) {
					//     g
					//   p   u
					// c       
				parent->_col = BLACK;
				uncle->_col = BLACK;
				grandfather->_col = RED;

				// 继续往上调整
				cur = grandfather;
				parent = cur->_parent;
			}
			else { // 情况2+3:u不存在/u存在且为黑,旋转+变色 在这种情况下就无法满足最短路径的两倍是大于最长路径的这个条件,因此需要进行旋转操作,从AVL树中可以得知旋转是有四种情况,需要分别对其进行处理
					//     g
					//   p   u
					// c 
				if (cur == parent->_left) {
					RotateR(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				else // cur == parent->_right {
					//     g
					//   p   u
					//     c
					RotateL(parent);
					RotateR(grandfather);
					cur->_col = BLACK;
					//parent->_col = RED;
					grandfather->_col = RED;
				}

				break;
			}
		}

		else { // grandfather->_right == parent
					//    g
					//  u   p
					//        c
			Node* uncle = grandfather->_left;
			// 情况1:u存在且为红,变色处理,并继续往上处理
			if (uncle && uncle->_col == RED) {
				parent->_col = BLACK;
				uncle->_col = BLACK;
				grandfather->_col = RED;

				// 继续往上调整
				cur = grandfather;
				parent = cur->_parent;
			}
			else { // 情况2+3:u不存在/u存在且为黑,旋转+变色
					//    g
					//  u   p
					//        c
				if (cur == parent->_right) {
					RotateL(grandfather);
					grandfather->_col = RED;
					parent->_col = BLACK;
				}
				else { // cur == parent->_left
					//    g
					//  u   p
					//    c
					RotateR(parent);
					RotateL(grandfather);
					cur->_col = BLACK;
					grandfather->_col = RED;
				}

				break;
			}
		}
	}

	_root->_col = BLACK;

	return true;
}

 红黑树的验证

 红黑树的检测分为两步:

  1. 检测其是否满足二叉搜索树(中序遍历是否为有序序列)
  2. 检测其是否满足红黑树的性质
bool IsBalance()
{
	// 检查根节点的颜色
	if (_root && _root->_col == RED) 
	{
		cout << "根节点颜色是红色" << endl;
		return false;
	}

	// 将最左路径作为基准值进行黑色节点的比较
	int benchmark = 0;
	Node* cur = _root;
	while (cur)
	{
		if (cur->_col == BLACK)
			++benchmark;
		cur = cur->_left;
	}

	// 连续红色节点
	return _Check(_root, 0, benchmark);
}

bool _Check(Node* root, int blackNum, int benchmark)
{
	// 检查黑色结点的数量,DFS走到空时将blackNum用来记录黑色结点的数量与benchmark进行比较
	if (root == nullptr)
	{
		if (benchmark != blackNum)
		{
			cout << "某条路径黑色节点的数量不相等" << endl;
			return false;
		}
		//cout << blackNum << endl; // 求每条路径黑色结点的数量
		return true;
	}

	if (root->_col == BLACK)
	{
		++blackNum;
	}

	// 检查连续的红色结点,从父亲开始检查儿子这种方法不是很好(孩子是有可能不存在的),从儿子开始检查父亲更加的便捷
	if (root->_col == RED
		&& root->_parent
		&& root->_parent->_col == RED)
	{
		cout << "存在连续的红色节点" << endl;
		return false;
	}

	return _Check(root->_left, blackNum, benchmark)
		&& _Check(root->_right, blackNum, benchmark);
}

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

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

相关文章

冲量在线出席2023鲲鹏昇腾开发者峰会,联合鲲鹏打造可信AIGC一体机,共筑产业数字根基

近日&#xff0c;以“创未来 享非凡”为主题的2023鲲鹏昇腾开发者峰会在东莞松山湖举办。鲲鹏昇腾开发者峰会是面向ICT领域开发者的技术盛会&#xff0c;旨在打造生态伙伴、开发者学习、交流的平台&#xff0c;帮助开发者深入了解鲲鹏、昇腾全栈技术&#xff0c;加速行业技术、…

超聚变携手冲量在线打造可信AIGC计算联合解决方案:软硬件高效协同之跃

金融行业作为全球经济的核心引擎&#xff0c;不断变革和创新是其发展的常态&#xff0c;在算力这一日趋成为数字经济时代的新型生产力的趋势下&#xff0c;围绕金融业数字化&#xff0c;业界展开了新一轮探索。 近日&#xff0c;2023中国国际金融展&#xff08;简称&#xff1…

uniapp app 实现qq登录、微信登录

一、申请 uniapp qq登录流程&#xff1a; 开通 | uni-app官网 申请微信登录可前往微信开发平台&#xff1a;微信开放平台 uniapp 微信登录流程&#xff1a; uni-app官网 申请qq登录可前往qq互联&#xff1a;QQ互联官网首页 这些都可以请运维同学帮我们申请&#xff0c;前…

【Linux】驱动内核调试,是需要几板斧的

目录 前言&#xff1a; 一、基础打印工具 &#xff08;1&#xff09;printk---最常用 ①Log Buffer: ②Console&#xff1a; ③RAM Console&#xff1a; &#xff08;2&#xff09;动态打印 ①动态打印与printk之间的区别联系 ②动态打印常用的例子 ③动态打印转为pri…

推荐系统---AUC / NDGG

目录&#xff1a; ROC / AUC1&#xff1a;坐标含义&#xff08;横坐标&#xff09;FPR&#xff1a;伪阳性率&#xff0c;分类器 “分类错误的负样本个数” 占 “总负样本个数” 的比例。&#xff08;纵坐标&#xff09;TPR&#xff1a;真阳性率&#xff0c;分类器 “分类正确的…

centos 7.6 安装mysql 5.7.35

centos 7.6 安装mysql 5.7.35 1、下载mysql安装包2、安装文档3、安装MySQL包4、安装后形成的配置文件和程序位置5、安装后设置5.1、修改MySQL root账户默认密码5.2、关闭系统防火墙 6、使用mysql 5.7.35 数据库6.1、命令行登录MySQL 5.7.35 数据库6.2、navicat连接mysql 5.7.35…

酒精和肠内外健康:有帮助还是有害?

谷禾健康 酒精与健康 饮酒作为一种特殊的文化形式&#xff0c;在我们国家有其独特的地位&#xff0c;在几千年的发展中&#xff0c;酒几乎渗透到日常生活、社会经济、文化活动之中。 据2018年发表的《中国饮酒人群适量饮酒状况》白皮书数据显示&#xff0c;中国饮酒人群高达6亿…

MS5208数模转换器可pin对pin兼容DAC128S085

DAC128S085 是一款功能齐全的通用八通道 12 位电压输出数模转换器 &#xff08;DAC&#xff09;&#xff0c;可采用 2.7V 至 5.5V 单电源供电&#xff0c;3V 时功耗为 1.95mW&#xff0c;5 V 时功耗为 4.85mW。DAC128S085 采用 16 引脚 WQFN 封装和 16 引脚 TSSOP 封装。WQFN 封…

Convolutional Neural network(卷积神经网络)

目录 Why CNN for Image&#xff1f; The whole CNN structure Convolution&#xff08;卷积&#xff09; Max Pooling Flatten CNN in Keras What does CNN learn&#xff1f; what does filter do what does neuron do what about output Deep Dream Application Pla…

数据库缓存服务——NoSQL之Redis配置与优化

一、缓存概念 缓存是为了调节速度不一致的两个或多个不同的物质的速度&#xff0c;在中间对速度较慢的一方起到加速作用&#xff0c;比如CPU的一级、二级缓存是保存了CPU最近经常访问的数据&#xff0c;内存是保存CPU经常访问硬盘的数据&#xff0c;而且硬盘也有大小不一的缓存…

测试用例的设计方法

目录 测试用例的设计方法 等价类&#xff1a; 等价类分为有效等价类与无效等价类 分类树 边界值&#xff1a; 语法测试 正面测试&#xff1a; 负面测试&#xff1a; 判定表测试 因果图&#xff1a; 场景法&#xff1a; 随机测试&#xff1a; 希望能起到帮助&#xf…

关于使用pyinstaller来打包PySide2程序中的问题

打包 pyinstaller 02.py --noconsole --hidden-import PySide2.QtXml 报错0&#xff1a;The ‘pathlib‘ package is an obsolete backport of a standard library package 分析&#xff1a;这个是因为笔者使用的conda的集成环境&#xff0c;这里面自带了打包程序&#xff0c…

Cocos creator小游戏实现套牛小游戏资源及代码

Cocos creator实现套牛小游戏资源及代码 一 安装CocosDashBoard二 新建2D项目RunCow1、管理项目目录2、搭建界面 三 上线微信小游戏1、上线微信小游戏2、Cocos Creator代码打包上传3、上线微信小游戏出现问题 Cocos creator小游戏实现套牛小游戏资源及代码 最近在学习Cocos Cre…

23案例P135-员工部门增删改查实现

一、准备工作 需要完成tlias的部门管理和员工管理 创建tlias数据库&#xff0c;导入 -- 部门管理 create table dept(id int unsigned primary key auto_increment comment 主键ID,name varchar(10) not null unique comment 部门名称,create_time datetime not null commen…

软件I2C读写MPU6050代码

1、硬件电路 SCL引到了STM32的PB10号引脚&#xff0c;SDA引到了PB11号引脚软件I2C协议&#xff1a; 用普通GPIO口&#xff0c;手动反转电平实现协议&#xff0c;不需要STM32内部的外设资源支持&#xff0c;故端口是可以任意指定MPU605在SCL和SDA自带了两个上拉电阻&#xff0c;…

漏刻有时地理信息系统说明文档(LOCKGIS、php后台管理、三端一体PC-H5-微信小程序、百度地图jsAPI二次开发、标注弹窗导航)

漏刻有时地理信息系统LOCKGIS 前言一、运行环境&#xff08;一&#xff09;环境检查&#xff08;二&#xff09;权限检查&#xff08;三&#xff09;函数支持&#xff08;四&#xff09;域名相关 二、核心代码&#xff08;一&#xff09;坐标展示&#xff08;二&#xff09;实时…

Excel快捷键大全(2023最新版总结)

案例&#xff1a;Excel快捷键大全 【作为一名打工人&#xff0c;我总是要用到Excel表格&#xff0c;大家平常在使用Excel时都有什么比较好用的快捷键推荐吗&#xff1f;】 Excel是一款功能强大的电子表格软件&#xff0c;可以用于数据管理、计算、分析和报表生成等多种任务。…

HDFS的数据流

1.HDFS写数据流程 &#xff08;1&#xff09;客户端通过Distributed FileSystem模块向NameNode请求上传文件&#xff0c;NameNode检查目标文件是否已存在&#xff0c;父目录是否存在。 &#xff08;2&#xff09;NameNode返回是否可以上传。 &#xff08;3&#xff09;客户端…

Shell系统编程三剑客之----AWK

目录 1.AWK工具简介 2.AWK的基本格式 3.AWK工作原理 4.常见的内建变量&#xff08;可直接用&#xff09; 二&#xff1a; AWK实例 1.按行输出文本 2.按字段输出文本 3.通过管道、双引号调用 Shell 命令 4.date命令输出时间 5. 查看内存使用占比 6.查看cpu使用占比 7.…

【linux】shell编程—快捷命令

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、快捷排序 - sort二、快捷去重 - uniq三、快捷替换 - tr四、快速裁剪 - cut五、文件拆分 - split六、文件合并 - paste七、变量扫描器 - eval 一、快捷排序 - so…