C++之红黑树

news2024/10/7 18:26:12

文章目录

  • 前言
  • 一、概念
  • 二、性质
  • 三、结点的定义
  • 四、红黑树的结构
  • 五、插入操作
    • 1.插入代码
    • 2.左单旋
    • 3.右单旋
    • 4.插入新结点的情况分析与总结
      • 第一步、按照搜索二叉树的规则插入新结点
      • 第二步、分析插入结点后红黑树的性质是否被破坏
      • 动态演示:
  • 六、验证红黑树
    • 1.检测是否满足二叉搜索树
    • 2.检测是否满足红黑树性质
  • 七、红黑树与AVL树的比较
  • 总结


前言

前面一节我们介绍了平衡搜索二叉树AVL树,我们知道,AVL树虽然查找效率很高,但是不能过多的修改,因为它为了保持平衡要不断的进行旋转。我们今天介绍的红黑树也是一种平衡搜索树,不过它所要求的平衡没有AVL树那么严格,因此对它进行修改操作时所要进行的旋转比AVL树要进行的旋转少。


一、概念

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

二、性质

  1. 每个结点不是黑色就是红色
  2. 根节点是黑色
  3. 如果一个结点是红色,那么它的两个孩子结点是黑色
  4. 对于每一个结点,从该节点到其所有后代叶子结点的简单路径上,均包含相同数目的黑色结点
  5. 每个叶子节点都是黑色(此处值空结点,即NIL结点)。

三、结点的定义

//结点的颜色
	enum color
	{
		RED,
		BLACK
	};
//结点的定义
	template<typename T>
	struct RBnode//红黑树的结点(三叉链)
	{
		RBnode(T data)
		:_data(data)
		, _parent(nullptr)
		, _left(nullptr)
		, _right(nullptr)
		, col(RED)
		{}
		T _data;//数据
		RBnode* _parent;//父节点(为了方便实现旋转)
		RBnode* _left;//左孩子
		RBnode* _right;//右孩子
		color _col;//颜色
	};

看到这里,相信大家会有一个疑问:为什么默认新插入的结点颜色是红色?
答:如果我们将新结点的颜色设置为黑色,那么它一定会违背性质4(即,对于每一个结点,从该节点到其所有后代叶子结点的简单路径上,均包含相同数目的黑色结点),这样我们就需要大幅度的在这棵树上进行调整(几乎需要所有路径进行调整),才能使它再次符合性质4;
如果我们将新结点颜色设置为红色,它有可能违背性质3(即,如果一个结点是红色,那么它的两个孩子结点是黑色),也有一定的可能不违背,即使违背性质3它所带来的后果比违背性质4严重性小很多,我们可以通过几次旋转(只对它所在的路径进行调整)使它重新符合性质3。
因为我们新插入一个结点,它不是黑色就是红色,因此一定会违反一条性质,我们选择违反后果较小的性质3。

四、红黑树的结构

五、插入操作

1.插入代码

		bool insert(const pair<K, V>& kv)
		{
			Node* newnode = new RBnode<K,V>(kv);
			if (!_root)//如果根节点为空,则新插入的结直接就是根节点
			{
				_root = newnode;
			}
			else
			{
				Node* cur = _root;
				Node* parent = cur;
				while (cur)
				{
					parent = cur;
					if (cur->_kv.first > kv.first)
					{
						cur = cur->_left;
					}
					else if (cur->_kv.first < kv.first)
					{
						cur = cur->_right;
					}
					else//树中已经有这个结点,插入失败
					{
						return false;
					}
				}
				cur = newnode;
				if (parent->_kv.first > cur->_kv.first)
				{
					parent->_left = cur;
				}
				else
				{
					parent->_right = cur;
				}
				cur->_parent = parent;
				Node* Grandpa = parent->_parent;
				Node* uncle = nullptr;
				while(Grandpa && parent->_col == RED)//如果父节点不是黑色,且父节点不是根结点
				{
					if (parent == Grandpa->_left)//定义叔叔结点
					{
						uncle = Grandpa->_right;
					}
					else
					{
						uncle = Grandpa->_left;
					}
					if (uncle->_col == RED)//如果叔叔存在且为红
					{
						Grandpa->_col = RED;
						parent->_col = uncle->_col = BLACK;
					}
					else//如果叔叔不存在,或者存在且为黑
					{
						//p是g的右孩子,c是p的右孩子(左单旋)
						if (parent == Grandpa->_right && cur == parent->_right)
						{
							Rotetal(Grandpa);
							Grandpa->_col = RED;
							parent->_col = BLACK;
						}
						//p是g的左孩子,c是p的左孩子(右单旋)
						else if (parent == Grandpa->_left && cur == parent->_left)
						{
							Rotetal(Grandpa);
							Grandpa->_col = RED;
							parent->_col = BLACK;
						}
						//p是g的左孩子,c是p的右孩子(左右双旋)
						else if (parent == Grandpa->_left && cur == parent->_right)
						{
							//先以parent为轴进行左单旋
							Rotetal(parent);
							//再以Grandpa为轴进行右单旋
							Rotetar(Grandpa);
							//更新颜色
							cur->_col = BLACK;
							Grandpa->_col = parent->_col = RED;
						}
						//p是g的右孩子,c是p的左孩子(右左双旋)
						else if (parent == Grandpa->_right && cur == parent->_left)
						{
							//先以parent为轴进行右单旋
							Rotetar(parent);
							//再以Grandpa为轴进行左单旋
							Rotetal(Grandpa);
							//更新颜色
							cur->_col = BLACK;
							Grandpa->_col = parent->_col = RED;
						}
						//旋转之后就符合性质4了,因此不用再继续更新
						break;
					}
					cur = parent;
					parent = Grandpa;
					Grandpa = Grandpa->_parent;
				}
				if (_root->_col == RED)//如果到最后更新到根节点,导致根节点为红色,为了满足性质2(根节点是黑色),就要将根结点置为黑色
				{
					_root->_col = BLACK;
				}
			}
			return true;
		}

2.左单旋

		//左单旋
		void Rotetal(Node* parent)
		{
			Node* SubR = parent->_right;
			Node* SubRL = SubR->_left;
			Node* Grandpa = parent->_parent;
			parent->_parent = SubR;
			parent->_right = SubRL;
			if (SubRL)
			{
				SubRL->_parent = parent;
			}
			SubR->_parent = Grandpa;
			if (!Grandpa)
			{
				_root = SubR;
				_root->_parent = nullptr;
			}
			else
			{
				if (parent == Grandpa->_left)
				{
					Grandpa->_left = SubR;
				}
				else
				{
					Grandpa->_right = SubR;
				}
			}
			SubR->_left = parent;
		}

3.右单旋

		//右单旋
		void Rotetar(Node* parent)
		{
			Node* SubL = parent->_left;
			Node* SubLR = SubL->_right;
			Node* Grandpa = parent->_parent;
			parent->_parent = SubL;
			parent->_left = SubLR;
			if (SubLR)
			{
				SubLR->_parent = parent;
			}
			SubL->_parent = Grandpa;
			if (!Grandpa)
			{
				_root = SubL;
				_root->_parent = nullptr;
			}
			else
			{
				if (parent == Grandpa->_left)
				{
					Grandpa->_left = SubL;
				}
				else
				{
					Grandpa->_right = SubL;
				}
			}
			SubL->_right = parent;
		}

4.插入新结点的情况分析与总结

第一步、按照搜索二叉树的规则插入新结点

如果该树是空树,就让新结点作为它的根节点。
先找到要插入的位置,比当前结点小就向左子树寻找,比当前结点大就向右子树寻找。

第二步、分析插入结点后红黑树的性质是否被破坏

新结点默认为红色,
1.如果双亲节点的颜色是黑色,则没有违反红黑树性质,不需要调整;
2.如果双亲节点的颜色是红色,则违反性质4需要进行调整。
为了方便分析,我们约定当前结点为cur©,当前节点的父节点为parent§,当前节点的祖父结点为Grandpa(g),当前结点的叔叔结点为uncle(c).。

  • 情况一:c为红色,p为红,g为黑,u存在且为红
    在这里插入图片描述
    只需要将p和u的颜色置为黑色,g的颜色置为红色。
    如果g是根节点,调整之后只需要将g改为黑色即可;
    如果g是子树,那么g一定有双亲结点,如果g的双亲结点为红色,就需要继续向上调整。
  • 情况二:cur为红,p为红,g为黑,u不存在/存在且为黑
    1. 如果u不存在,则说明cur是新增节点。因为如果cur不是新增节点,那么cur和p一定有一个是黑色,那么就不满足性质4(每条路径上的黑色结点的个数相同);
    1. 如果u存在且为黑,则说明cur原本就存在且为黑。现在是红色是因为cur所在子树新增节点导致向上调整颜色的过程中将cur置为红色。
      这种情况有四种可能:
  • p是g的左孩子,c是p的左孩子;(要进行右单旋)
    以g为轴进行右单旋:
    在这里插入图片描述
    更新结点p为黑色,cur和g为红色。
  • p是g的右孩子,c是p的右孩子;(要进行左单旋)
    以g为轴进行左单旋:
    在这里插入图片描述
    更新结点p为黑色,cur和g为红色。
  • p是g的左孩子,c是p的右孩子;(要进行左右双旋)
    先以p为轴进行左单旋,再以g为轴进行右单旋:
    在这里插入图片描述
    更新结点cur为黑色,p和g为红色。
  • p是g的右孩子,c是p的左孩子。(要进行右左双旋)
    先以p为轴进行左单旋,再以g为轴进行右单旋:
    在这里插入图片描述
    更新结点cur为黑色,p和g为红色。

动态演示:

升序:
在这里插入图片描述
降序:
在这里插入图片描述
随机插入构建红黑树:
在这里插入图片描述

右旋转:
在这里插入图片描述
左旋转:
在这里插入图片描述

六、验证红黑树

验证红黑树分为两步:

1.检测是否满足二叉搜索树

中序遍历是否为有序序列

2.检测是否满足红黑树性质

代码如下:

		bool IsValidRBTree()//验证是否为红黑树
		{
			Node* pRoot = GetRoot();
			// 空树也是红黑树
			if (nullptr == pRoot)
				return true;
			// 检测根节点是否满足情况
			if (BLACK != pRoot->_col)
			{
				cout << "违反红黑树性质二:根节点必须为黑色" << endl;
				return false;
			}
			// 获取任意一条路径中黑色节点的个数
			size_t blackCount = 0;
			Node* pCur = pRoot;
			while (pCur)
			{
				if (BLACK == pCur->_col)
					blackCount++;
				pCur = pCur->_left;
			}
			// 检测是否满足红黑树的性质,k用来记录路径中黑色节点的个数
			size_t k = 0;
			return _IsValidRBTree(pRoot, k, blackCount);
		}
		bool _IsValidRBTree(Node* pRoot, size_t k, const size_t blackCount)
		{
			//走到null之后,判断k和black是否相等
			if (nullptr == pRoot)
			{
				if (k != blackCount)
				{
					cout << "违反性质四:每条路径中黑色节点的个数必须相同" << endl;
					return false;
				}
				return true;
			}
			// 统计黑色节点的个数
			if (BLACK == pRoot->_col)
				k++;
			// 检测当前节点与其双亲是否都为红色
			Node* pParent = pRoot->_parent;
			if (pParent && RED == pParent->_col && RED == pRoot->_col)
			{
				cout << "违反性质三:没有连在一起的红色节点" << endl;
				return false;
			}
			return _IsValidRBTree(pRoot->_left, k, blackCount) &&
				_IsValidRBTree(pRoot->_right, k, blackCount);
		}

七、红黑树与AVL树的比较

红黑树和AVL树都是高效的平衡二叉树,增删查改的时间复杂度都是O( l o g 2 N log_2 N log2N),红黑树不追求绝对平衡,只要保证最长路径不超过最短路径的两倍。相对而言,插入和旋转的次数更少,在经常进行增删的结构中性能比AVL树更优,而且红黑树的实现比AVL树简单,因此更加常用。


总结

以上就是今天要讲的内容,本文介绍了C++中红黑树的相关概念。本文作者目前也是正在学习C++相关的知识,如果文章中的内容有错误或者不严谨的部分,欢迎大家在评论区指出,也欢迎大家在评论区提问、交流。
最后,如果本篇文章对你有所启发的话,希望可以多多支持作者,谢谢大家!

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

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

相关文章

口令暴力破解--Telnet协议暴力破解、数据库暴力破解与远程桌面暴力破解

Telnet协议暴力破解 Telnet Telnet协议是TCP/IP协议族中的一员&#xff0c;是Internet远程登陆服务的标准协议和主要方式。它为用户提供了在本地计算机上完成远程主机工作的能力。要开始一个telnet会话&#xff0c;必须输入用户名和密码来登录服务器。而一般服务器不会对用户名…

【数据结构】6.4 AVL树(C++)

【数据结构】——6.4 AVL树 没有学过二叉搜索树&#xff08;也叫二叉排序树或二叉查找树&#xff09;的小伙伴们建议先学习一下&#xff0c;这样阅读会更轻松哦 点我学习二叉搜索树 目录一、AVL树的概念1. 二叉搜索树的问题2. AVL树的性质二、AVL树实现平衡的方法1. 更新平衡因…

【音视频第11天】GCC论文阅读(2)

A Google Congestion Control Algorithm for Real-Time Communication draft-alvestrand-rmcat-congestion-03论文理解 看中文的GCC算法一脸懵。看一看英文版的&#xff0c;找一找感觉。 目录Abstract1. Introduction1.1 Mathematical notation conventions2. System model3.Fe…

Shader 海面/水面

首先用Terrain在场景中随便做个地形&#xff0c;当作海底 上面加个Plane作为海面 实现海水效果要考虑海水深度对颜色的影响&#xff0c;法线移动形成波浪&#xff0c;菲涅尔&#xff0c;高光等效果 深度 海水深的地方颜色深&#xff0c;浅的地方颜色浅&#xff0c;所以海边和…

fastDFS文件管理系统在linux下部署

1.概述 fastDFS分布式文件系统包括三个中要部分&#xff1a;追踪器、存储节点、客户端&#xff0c;可以使用文件存储&#xff0c;文件同步&#xff0c;文件访问等功能&#xff0c;用来存储大容量数据 存储节点集群&#xff1a; 横向扩容&#xff1a;增加存储容量 纵向扩容&…

liunx系统(VMware Workstation Pro)详细安装配置docker

​ 安装东西前要知道docker是什么,以及docker能都干什么,文章都是本人亲测然后写的过程. http://t.csdn.cn/iqbGg 博客文章链接详细介绍docker,以及部署MySQL,nginx等配置 一. liunx系统(VMware) 安装Docker 1. Docker中文网地址: Docker中文网 官网 (p2hp.com) 2. 打开VM…

50 Projects 50 Days - Progress Steps 学习记录

50 Projects 50 Days不使用任何前端框架&#xff0c;适合初学者练手&#xff0c;巩固前端基础&#xff0c;在这里记录一下学习过程&#xff0c;尤其是一些细节上的问题。 项目地址 Progress Steps 展示效果 Progress Steps 实现思路 进度条和结点分开处理&#xff1a; 1…

深入理解计算机系统第九章知识点总结

第九章 一些术语 PA(physical address)&#xff1a;物理地址VA(virtual address)&#xff1a;虚拟地址MMU(memory management unit)&#xff1a;内存管理单元VP(virtual page)&#xff1a;虚拟页PP(physical page)&#xff1a;物理页/页帧SRAM&#xff1a;表示位于CPU和主存之…

详解Spring事务

目录 1.声明式事务 1.1.概述 1.2.使用 1.2.1.建表 1.2.2.maven依赖 1.2.3.配置 1.2.4.业务 1.2.5.测试 2.事务的传播行为 1.声明式事务 1.1.概述 spring中事务分为两种&#xff1a; 1.编程式事务&#xff0c;通过写代码来实现&#xff0c;每一步。 2.声明式事务&am…

华为手表开发:WATCH 3 Pro(15)传感器订阅加速度计

华为手表开发&#xff1a;WATCH 3 Pro&#xff08;15&#xff09;传感器订阅加速度计初环境与设备加速度传感器介绍与说明鸿蒙开发文件夹&#xff1a;文件重点新增展示的文本标记index.hmlindex.cssindex.js初 希望能写一些简单的教程和案例分享给需要的人 鸿蒙可穿戴开发 环…

Elasticsearch:索引状态是红色还是黄色?为什么?

在我之前文章 “Elasticsearch&#xff1a;如何调试集群状态 - 定位错误信息” 中&#xff0c;我有详细介绍如何调试集群状态。在今天的文章中&#xff0c;我将详细介绍如何故障排除和修复索引状态。 Elasticsearch 是一个伟大而强大的系统&#xff0c;特别是创建一个可扩展性极…

C++、STL标准模板库和泛型编程 ——关联式容器 (侯捷)

C、STL标准模板库和泛型编程——关联式容器 &#xff08;侯捷&#xff09;&#xff08; 持续更新&#xff01;&#xff01;&#xff01;&#xff09; 关联式容器rb_tree 容器set、multiset 容器map、multimap容器C、STL标准模板库和泛型编程——序列式容器 &#xff08;侯捷&am…

go+vue——go入门

govue技术选择入坑理由需要搭建前后端&#xff0c;Java 0 基础 &#xff0c;环境容易出现问题&#xff1b;GO上手快&#xff0c;问题少推荐&#xff1a;【七米】代码博客搭建Go语言开发环境下载 并 安装检查是否安装好&#xff1f;GOPROXY 非常重要&#xff08;帮你下载国外、G…

分布式锁Redision

目录 1.ab工具(压测工具)的安装 2.前置 3.优化 3.1synchronized修饰代码方法/代码块 3.2分布式锁事务的解决方案 3.3Redis实现锁问题 3.3.1 set ex方式 3.3.2 set ex方式设置过期时间 3.3.3单redis结点的解决UUID和LUA脚本 3.3.4redission解决分布式锁 4.Redission解…

数据结构:顺序表

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;本期来给大家解读一下数据结构方面有关顺序表的相关知识点&#xff0c;如果看完之后对你有一定的启发&#xff0c;那么请留下你的三连&#xff0c;祝大家心想事成&#xff01; C语言专栏&#xff1a;C语言&#xff1a;从入门…

阿里的Leader为什么牛逼?秘密都在“三板斧”里...

许多人都觉得&#xff0c;啊里出来的Leader&#xff0c;做事情都很有方法、有套路、有结果&#xff0c;秘密究竟在哪里&#xff1f;其实一个人的牛逼&#xff0c;首先是方法论的牛逼。本文就来聊聊&#xff0c;阿里Leader们都要学习的管理方法论&#xff0c;俗称阿里“三板斧”…

《MySQL系列-InnoDB引擎37》索引与算法-全文检索

全文检索 1 概述 对于B树的特点&#xff0c;可以通过索引字段的前缀进行查找。例如如下的查询方式是支持B树索引的,只要name字段添加了B树索引&#xff0c;就可以利用索引快速查找以XXX开头的名称。 select * from table where name like XXX%; 而如下这种情况不适合私有B索…

BUUCTF-sql注入联合查询的创建虚拟表-词频-steghide的使用

第七周第三次 目录 WEB [GXYCTF2019]BabySQli [GXYCTF2019]BabyUpload Crypto 世上无难事 old-fashion ​Misc 面具下的flag 九连环 WEB [GXYCTF2019]BabySQli 这是一道很新的题目 我们打开环境 发现登入注册界面 先看看源码有没有提示 发现有一个 php文件 进入…

Spark 对hadoopnamenode-log文件进行数据清洗并存入mysql数据库

一.查找需要清洗的文件 1.1查看hadoopnamenode-log文件位置 1.2 开启Hadoop集群和Hive元数据、Hive远程连接 具体如何开启可以看我之前的文章&#xff1a;(10条消息) SparkSQL-liunx系统Spark连接Hive_难以言喻wyy的博客-CSDN博客 1.3 将这个文件传入到hdfs中&#xff1a; hd…

OpenAI Translator | 基于ChatGPT API全局翻译润色解析及ORC上传图像翻译插件

简介 OpenAI Translator&#xff0c;一款基于 ChatGPT API 的划词翻译的浏览器插件和跨平台桌面端应用&#xff0c;使用 ChatGPT API 进行划词翻译和文本润色&#xff0c;借助了 ChatGPT 强大的翻译能力&#xff0c;帮助用户更流畅地阅读外语和编辑外语&#xff0c;允许跨 55 …