数据结构——红黑树

news2024/12/25 1:06:29

红黑树

  • 概念与性质
  • 树节点的定义
  • 插入
  • 红黑树的验证
  • 红黑树与AVL树的对比

概念与性质

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

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

为什么满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点个数的两倍?

我们来考虑最好和最坏的情况:
最好:全都是黑节点,这是满二叉树。时间复杂度:O(logN)
在这里插入图片描述
最坏:最不平衡,但是又不破坏性质,也就是比最好情况多找一倍的量,2 logN,这个量对于计算机而言是非常容易的。时间复杂度:O(logN)
在这里插入图片描述
对于这两种情况,如果其中一条路径添加黑结点,那么其他路径也要添加黑结点,如果是最坏的情况22号结点添加了一个红色结点是不允许的,所以只要遵循红黑树的性质就不会右最长路径超过最短路径二倍的问题。

这棵树和AVL树不同的是没有平衡因子,多了一个颜色变量。
相比较于AVL树,红黑树的旋转更少一些,AVL总是要旋转,也是会影响效率的。

树节点的定义

enum Color//利用枚举来给红黑树配色
{
	RED,
	BLACK
};
template<class K, class V>
struct RBTreeNode
{
	RBTreeNode(const T& data)
		:_data(data)
		, _color(RED)//这里一定要给红色,如果给黑色其他路径就要涉及到也要加黑色结点,更加麻烦
		,_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
	{}

	RBTreeNode* _left;
	RBTreeNode* _right;
	RBTreeNode* _parent;
	pair<K, V> _data;
	Color _color;//结点的配色
};

插入

插入的第一步和搜索二叉树的一样,先找到合适的位置。
第二步就是判断是否符合红黑树的性质,因为插入的新节点是红色的,所以分为以下几个情况考虑。
图中的g代表grandfather,p是parent,u是uncle,c是cur。
情况一: cur为红,p为红,g为黑,u存在且为红。
在这里插入图片描述
c为新增节点,p为红色,u也为红色,g点为黑色,这个时候应该将p和u变成黑色,g变成红色。
在这里插入图片描述
第一种情况,无论c插入的地方是p的子树还是u的子树都无所谓。
这里还要注意,如果g结点的上面还是红色结点,这里就需要继续向上调整。
这个时候就要将g赋值给c,p等于c的parent指向的结点。

情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑。
在这里插入图片描述
在这里插入图片描述
首先对g右单旋,然后将p变为红色,g变为黑色。
这个情况无论是对于局部树还是根节点都不会使上面的发生任何变化,因为原来g结点的位置变成了p结点,但是颜色还是黑色。
这里就算u结点什么也没有,也是按照旋转+变色的方式来解决问题。
情况三: cur为红,p为红,g为黑,u不存在/u存在且为黑
在这里插入图片描述
p为g的左孩子,cur为p的右孩子,则针对p做左单旋转;
相反,p为g的右孩子,cur为p的左孩子,则针对p做右单旋转
则转换成了情况2

其实这三种情况看下来之后我们发现,最重要的是叔叔节点。
代码实现

	bool Insert(const pair<K, V>& data)
	{
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_color = BLACK;
			return true;
		}
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (cur->_data.first > data.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_data.first < data.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}
		cur = new Node(data);//这里默认是红色结点
		if (parent->_data.first > data.first)
		{
			cur->_parent = parent;
			parent->_left = cur;
		}
		else if (parent->_data.first < data.first)
		{
			cur->_parent = parent;
			parent->_right = cur;
		}
		//如果父节点为空就代表cur是根节点,没必要调整了
		//还要判断cur结点是否与父节点的颜色均为红色
		while (parent && parent->_color == RED)
		{
			Node* grandfather = parent->_parent;//祖父结点
			if (parent == grandfather->_left)//新增结点在祖父左
			{
				Node* uncle = grandfather->_right;
				//情况一
				if (uncle && uncle->_color == RED)//这里不要忘记验证uncle的存在
				{
					parent->_color = BLACK;
					uncle->_color = BLACK;
					grandfather->_color = RED;
					cur = grandfather;//最后让cur等于祖父结点的位置
					parent = cur->_parent;
				}
				else
				{
					if (parent->_left == cur)//情况二
					{
						RotateR(grandfather);//右单旋
						grandfather->_color = RED;
						parent->_color = BLACK;
					}
					else if (parent->_right == cur)//情况三
					{
						RotateL(parent);//左单旋
						RotateR(grandfather);//右单旋
						cur->_color = BLACK;
						grandfather->_color = RED;
					}
					break;//第二种和第三种情况变完之后因为最上面的组节点变为黑,所以这里跳出循环
				}
			}
			else//新增结点在祖父右
			{
				Node* uncle = grandfather->_left;
				if (uncle && uncle->_color == RED)//情况一
				{
					uncle->_color = BLACK;
					parent->_color = BLACK;
					grandfather->_color = RED;
					cur = grandfather;
					parent = cur->_parent;
				}
				else {
					if (cur == parent->_right)//情况二
					{
						RotateL(grandfather);
						grandfather->_color = RED;
						parent->_color = BLACK;
					}
					else if (cur == parent->_left)//情况三
					{
						RotateR(parent);
						RotateL(grandfather);
						cur->_color = BLACK;
						grandfather->_color = RED;
					}
					break;
				}
			}
		}
		_root->_color = BLACK;
		return true;
	}
	void RotateL(Node* parent)//左单旋
	{
		Node* pparent = parent->_parent;
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;

		subR->_left = parent;
		parent->_parent = subR;

		if (pparent)
		{
			if (pparent->_left == parent)
				pparent->_left = subR;
			else
				pparent->_right = subR;
		}
		else
		{
			_root = subR;

		}
		subR->_parent = pparent;
	}
	void RotateR(Node* parent)//右单旋
	{
		Node* pparent = parent->_parent;
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

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

		subL->_right = parent;
		parent->_parent = subL;

		if (pparent)
		{
			if (pparent->_left == parent)
				pparent->_left = subL;
			else
				pparent->_right = subL;
		}
		else
		{
			_root = subL;
		}
		subL->_parent = pparent;
	}

红黑树的验证

红黑树的验证需要去检验不能是两个连续的红色结点,并且每条路径上的黑色节点相同。
这里解决的方法是添加两个参数,一个参数用来计算每条路径上的黑色结点,另外一个值用来储存某一条路径的黑色结点的个数。

	bool _IsBalanceTree(Node* root, int k, int sum)//验证
	{
		if (root == nullptr)
		{
			if (k == sum)//这里代表当前路径点和最左边的路径点相同
				return true;
			else
			{
				cout << "每条路径上黑色结点数量不同" << endl;
			}
			return false;
		}
		if (root->_color == BLACK)
			k++;
		if (root->_parent && root->_parent->_color == RED && root->_color == RED)
		{
			cout << root->_parent->_data.first << endl;
			return false;
		}
		return _IsBalanceTree(root->_left, k, sum)&&_IsBalanceTree(root->_right, k, sum);
	}

在这里插入图片描述

红黑树与AVL树的对比

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

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

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

相关文章

数字IC设计——功耗分析

一、概述 芯片的整体功耗很难通过简单的电流&#xff0c;电压或者电阻值的的相乘来计算。其原因在于&#xff0c;由于芯片作为具有复杂功能的器件&#xff0c;其功耗会根据其不同时段的不同行为&#xff0c;不同的外部条件而发生很大的变化。 1.1 功耗的分类 数字IC芯片的功…

Three.js+TypeScript+Webpack学习记录(二)

使用环境参考 Node.js v16.19.1 正文 跟着文档画个线 看看 Three 的官方文档&#xff0c;起步 -> 画线 -> 没了&#xff1f;&#xff01;&#xff01; 不管怎么说&#xff0c;先画个线吧。 import * as THREE from threeconst scene new THREE.Scene() const camer…

HummerRisk V1.0 开发手册(微服务版)

HummerRisk 是开源的云原生安全平台&#xff0c;以非侵入的方式解决云原生环境的安全和治理问题。核心能力包括混合云的安全治理和容器云安全检测。 本文将介绍HummerRisk 1.0以后的开发准备工作。 v1.0.0 以后的版本&#xff0c;代码在 dev 分支。欢迎大家在 dev 开发分支提…

多通道振弦传感器无线采集仪 数字传感器起始通道分配

多通道振弦传感器无线采集仪 数字传感器起始通道分配 寄存器 DS_CHNUM(299)用于设置读取到的数字传感器数据从哪个通道开始占用&#xff0c;默认为 1。 单个数字传感器占用的通道数量与具体的传感器类型有关&#xff0c;例如&#xff1a;每个激光测距仪会占用 1 个通道&#xf…

Linux Shell 实现一键部署二进制go+caddy+filebrowser

filebrowser filebrowser 是一个使用go语言编写的软件&#xff0c;功能是可以通过浏览器对服务器上的文件进行管理。可以是修改文件&#xff0c;或者是添加删除文件&#xff0c;甚至可以分享文件&#xff0c;是一个很棒的文件管理器&#xff0c;你甚至可以当成一个网盘来使用。…

HCIE Datacom认证学什么内容

什么HCIE 什么是HCIE&#xff1f;HCIE的全称是Huawei Certified ICT Expert&#xff08;华为认证ICT技术专家&#xff09;。 华为认证是行业中最严谨的认证&#xff0c;含金量与行业认可度都较高。在众多的IT行业认证中&#xff0c;HCIE被誉为“网络界的博士”。 华为认证HCI…

深度学习 - 41.Word2vec、EGES 负采样实现 By Keras

目录 一.引言 二.实现思路 1.样本构建 2.Word2vec 架构 3.EGES 架构 4.基于 NEG 的 Word2vec 架构 三.Keras 实现 Word2vec 1.样本构建 2.模型构建 3.向量获取 四.keras 实现 EGES 1.样本构建 2.模型构建 3.Dot Layer 详解 3.1 init 方法 3.2 call 方法 3.3 完…

4月18号软件更新资讯合集

ModStartCMS v6.2.0&#xff0c;VIP 权益配置功能、界面 UI 优化升级 ModStart 是一个基于 Laravel 模块化极速开发框架。模块市场拥有丰富的功能应用&#xff0c;支持后台一键快速安装&#xff0c;让开发者能快的实现业务功能开发。 系统完全开源&#xff0c;基于 Apache 2.…

4月19号软件更新资讯合集....

JavaWeb 微服务前后端分离 EurekaEleVue 版 v1.5.0 发布 v1.5.0 更新如下&#xff1a; 1、解决 token 过期无法跳转至登录页的问题&#xff1b; 2、授权服务进行重构与优化&#xff1b; 一款 Java 语言基于 SpringCloud、SpringSecurity、OAuth2、Eureka、Vue、ElementUI、…

Docker实战笔记1-基础

转载请标明出处&#xff1a;http://blog.csdn.net/zhaoyanjun6/article/details/130181636 本文出自【赵彦军的博客】 文章目录 官网下载安装安装Docker 镜像镜像分层存储 容器常用命令docker infodocker imagesdocker versiondocker ps &#xff1a;查看容器docker ps -a查看容…

如何检查设置的IP是否有效?Storm proxies动态代理IP好用吗?

检查设置的IP是否有效可以通过以下几种方式&#xff1a; 发起网络请求&#xff1a;可以使用HTTP客户端库&#xff08;例如Python的Requests库&#xff09;或者命令行工具&#xff08;例如curl&#xff09;来发起网络请求&#xff0c;使用设置的IP作为代理IP&#xff0c;然后查看…

读SQL进阶教程笔记12_地址与三值逻辑

1. SQL和数据库都在极力提升数据在表现层的抽象度&#xff0c;以及对用户隐藏物理层的概念 2. 关系模型是为摆脱地址而生的 2.1. “地址”不仅包括指针操作的地址&#xff0c;还包括数组下标等 3. 一个优雅的数据结构胜过一百行杂耍般的代码 3.1. 精巧的数据结构搭配笨拙的…

数据结构入门——顺序表(保姆级教程,增,删,改,查)

1.什么是顺序表 1.顺序表&#xff1a;可动态增长的数组&#xff0c;要求数据是连续存储的 2.顺序表的定义&#xff1a; 静态顺序表&#xff1a;使用定长数组存储元素&#xff08;缺点&#xff1a;小了不够用&#xff0c;大了还浪费&#xff09; 动态顺序表&#xff1a;可根…

744. 寻找比目标字母大的最小字母

给你一个字符数组 letters&#xff0c;该数组按非递减顺序排序&#xff0c;以及一个字符 target。letters 里至少有两个不同的字符。 返回 letters 中大于 target 的最小的字符。如果不存在这样的字符&#xff0c;则返回 letters 的第一个字符。 示例 1&#xff1a; 输入: le…

3. VBA术语

在本章中&#xff0c;将介绍常用的Excel VBA术语。这些术语将在很多的模块中使用&#xff0c;因此理解其中的每一个术语都很重要。 3.1 模块 模块是编写代码的区域。如下图中&#xff0c;这是一个新的工作簿&#xff0c;因此没有任何模块。 要插入模块&#xff0c;请导航到插…

Cell--瘤内微生物将开辟新疗法

2023年4月13日&#xff0c;弗雷德哈钦森癌症中心的微生物学家Susan Bullman教授在《Cell》杂志发表了关于肿瘤内微生物群的评论。 微生物群是肿瘤微环境的一个组成部分 在患者的肿瘤内部&#xff0c;恶性细胞处在一个复杂的生态系统中&#xff0c;周围是正常细胞的网络&#xf…

socks5与http代理如何转化?stormproxies怎么解决?

Socks5和HTTP代理之间可以通过一些工具或软件进行转化&#xff0c;具体的方法如下&#xff1a; 使用ProxyCap&#xff1a;ProxyCap是一款常用的代理工具&#xff0c;可以将Socks5代理转化为HTTP代理。在ProxyCap中设置Socks5代理的服务器地址和端口&#xff0c;然后在本地设置H…

Java应用高性能的方法和思路

Java应用高性能的方法和思路 目录概述需求&#xff1a; 设计思路实现思路分析 参考资料和推荐阅读 Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0c;skip hardness,make a better result,wait for change,challenge …

软件测试基础概念

1.软件测试的生命周期 需求分析-》测试计划-》测试设计-》测试开发-》测试执行-》测试评估 需求分析&#xff1a;分析需求是否合理和正确 计划:确定软件由谁测试&#xff1f; 什么时候开始测试&#xff0c;什么时候结束测试 测试那些模块 测试设计和测试开发阶段&#xff1a;…

4.19~20(总结)

项目越到后面&#xff0c;推进越难&#xff0c;已经到了发送消息这里了&#xff0c;这个做完&#xff0c;基本上也就完成得七七八八了&#xff0c;后面只需要优化了&#xff0c;但是现在卡在这里就还是有点难搞。 首先&#xff0c;我的界面已经成这样了。 我的打算是这样可以搜…