【数据结构】红黑树——领略天才的想法

news2024/9/24 15:20:26

个人主页:东洛的克莱斯韦克-CSDN博客  

祝福语:愿你拥抱自由的风       

目录

二叉搜索树

AVL树

红黑树概述

性质详解

效率对比

旋转操作

元素操作

代码实现


二叉搜索树

【数据结构】二叉搜索树-CSDN博客

AVL树

【数据结构】AVL树——平衡二叉搜索树-CSDN博客

红黑树概述

概念

在二叉搜索树的基础上符合一下性质便是红黑树

每一个节点不是红色就是黑色

根节点一定是黑色

空节点一定是黑色

父亲节点和孩子节点不能是连续的红色节点

每一条根节点至空节点路径上的黑色节点的数量一定相等

性质详解

理解路径

红黑树中,一条完整路径不是从叶子节点溯源至根节点,而是从空节点溯源至根节点

理解最长和最短路径

如果全是黑色节点,红黑树就是一颗满二叉树,因为每条路径的黑色节点数量相等

那么这颗树的每条完整路径都是最短路径,

如果在一条路径上红黑节点间隔(不允许连续的有红色节点),那么该路径为最长路径。

红黑树规则

那么最长路径是最短路径的两倍。这便是红黑树的平衡规则。

满二叉树的平衡条件是左右子树高度差为0,AVL树的平衡条件是左右子树高度差小于等于 1,相比于前两棵树平衡条件,红黑树是一种弱平衡

红黑树和AVL树一样,只要保证自己的平衡规则不被打破,就能使自己不退化为类似于链表的结构。

退化成类似链表的结构——插入的数据接近有序或插入大量的数据不够随机或进行了一些插入删除等操作。

效率对比

查找效率

从直觉上讲,红黑树只是维持一种弱平衡,在最坏情况下,红黑树的高度是AVL树高度的两倍,那么红黑树查找数据的效率也应该比AVL树低两倍才对,为什么我们认为红黑树是一种更优的数据结构呢?下面小编带大家算笔账

2的40次方是一万亿,也就是说用满二叉树存一万亿个数据高度为40。AVL树是有高度差的,所以最坏情况下会查找40多次,而红黑树最坏情况下会找80多次。

那么对于cpu而言,找40多次和找80多次有区别吗?答案是没有的,现在的cpu每秒钟可以运算十亿次甚至几百亿。

可以理解为,在查找数据的效率上AVL树和红黑树是一样的。那么,红黑树的优势在哪里呢?

插入删除效率

不管是红黑树还是AVL树,如果打破平衡都需要旋转这一操作恢复平衡,旋转所付出的时间复杂度为O(1)。对于AVL树而言,需要溯源更新平衡因子,对于红黑树而言,需要溯源更新节点颜色,溯源更新最坏情况下是从叶子节点更新到根节点,所付出的时间复杂度为O(logN)

因为AVL树的高度差小于等于1,平衡很容易被打破,要维持平衡就需要不断地付出O(1)O(logN)来维持平衡。

那么红黑树维持弱平衡就不需要总是付出这样地代价,所以红黑树是一种更优的数据结构

旋转操作

旋转操作不是AVL树或红黑树特有的,旋转一次的本质是让二叉搜索树的某棵子树的高度减一

对于红黑树而言,最长路径是最短路径的二倍加一,就意味着打破平衡,需要通过旋转让最长路径上的某棵子树高度减一来恢复平衡。旋转后需要更新节点的颜色,具体要怎么控制颜色下面细讲,现在看一下旋转操作吧

左单旋:新节点插入较高右子树的右侧——对fathernode

void RevolveLeft(node *& fathernode)//左单旋      
{
	node* cur = fathernode->_right; //父亲节点的右孩子
 
	fathernode->_right = cur->_left; //更改指向关系
 
	if (cur->_left != nullptr) //特殊情况
		cur->_left->_FatherNode = fathernode;//更改指向关系
 
	cur->_FatherNode = fathernode->_FatherNode;//更改指向关系
 
	if (fathernode->_FatherNode != nullptr) //为空是特殊情况,
	{
 
		if (fathernode->_FatherNode->_right == fathernode)
		{
			fathernode->_FatherNode->_right = cur;//更改指向关系
		}
		else
		{
			fathernode->_FatherNode->_left = cur;//更改指向关系
		}
 
	}
 
	cur->_left = fathernode;//更改指向关系
 
	fathernode->_FatherNode = cur;//更改指向关系
 
 
}

右单旋:新节点插入较高左子树的左侧——对fathernode

void RevolveRight(node *& fathernode) //右单旋
{
	node* cur = fathernode->_left; //父亲节点的左节点
 
	fathernode->_left = cur->_right;//更新指向关系
 
	if (cur->_right != nullptr) //特殊情况
		cur->_right->_FatherNode = fathernode;//更新指向关系
 
	cur->_FatherNode = fathernode->_FatherNode;//更新指向关系
 
	if (fathernode->_FatherNode != nullptr)//特殊情况
	{
 
		if (fathernode->_FatherNode->_right == fathernode)
		{
			fathernode->_FatherNode->_right = cur;//更新指向关系
		}
		else
		{
			fathernode->_FatherNode->_left = cur;//更新指向关系
		}
 
	}
 
 
	cur->_right = fathernode;//更新指向关系
	fathernode->_FatherNode = cur;//更新指向关系
 
}

左右双旋:新节点插入在较高左子树的右侧——先对fathernodeL左单旋再对fathernodeLR右单旋

右左双旋:新节点插入再较高右子树的左侧——先对fathernodeR右单旋再对fathernodeRL左单旋

元素操作

我们再来理解一下红黑树两条核心性质

父亲节点和孩子节点不能是连续的红色节点

每一条根节点至空节点路径上的黑色节点的数量一定相等

红黑树插入的新节点应该是黑色还是红色呢?

也就是说,插入红色节点可能会破坏第一条性质,插入黑色节点会破坏第二条性质。第一条性质被破坏只影响了当前路径,而第二条性质被破坏影响的是所有路径。所以插入新节点的颜色是红色

如果新插入节点的父亲节点是黑色,那么不会破坏任何性质,如果新插入节点的父亲节点是红色该怎么办呢?

颜色管理

下面介绍红黑树如何通过管理颜色来判断是否需要旋转,为了方便讨论讨论,给以下节点起个别名,

父亲节点:Father

孩子节点:child(父亲节点的孩子节点)

祖父节点:grandfather(父亲节点的父亲节点)

叔叔节点:uncle(如果父亲节点是祖父节点的左孩子,叔叔节点就是祖父节点的右孩子,反之亦然)

如果新节点的父亲节点为黑,插入成功。如果父亲节点为红,需要溯源更新颜色,规则如下:

如果uncle存在且为红色,Father和uncle变为黑色,grandfather变为黑色,让grandfather变为child,继续溯源更新

如果更新至整棵树的根节点(Father为空),让根节点置黑,或uncle为空或uncle黑色,停止溯源更新(uncle为空或uncle黑色后面会讨论)。

如果uncle不存在,或uncle存在且为黑,说明红黑树的平衡被打破了,此时就不需要溯源更新颜色了,需要旋转来恢复红黑树的平衡,旋转之后再更新Father,grandfather或child的颜色

右单旋颜色更新:

Father成为了根需要变成黑色节点,Father旋转之前的孩子节点为黑,该孩子会成为grandfather左孩子,grandfather需要变成红节点。

为什么Father旋转之前的孩子节点为黑呢?因为数据是一个一个插入的,新节点只会和一条路径上的父亲节点冲突,如果这是一颗正常的红黑树,Father旋转之前的孩子节点只能为黑色。Father作为根是黑色的,Father的孩子节点也只能是红色的。下面的旋转也一样

左单旋颜色更新:

还是和右单旋一样,Father成了根需要变成黑色,grandfather需要变成红色。

双旋颜色更新:

无论是左右双旋还是右左双旋,最终都是child变成了根,grandfather和Father变成了child的左右孩子,grandfatjie作为孩子需要变成红色。

代码实现

【C++】详解C++的模板-CSDN博客

enum Color { RED, BLACK };    

template <class K>
class RBTreeNode
{
public:

	
	typedef RBTreeNode <K> node_type;   

	K _ky;   
	node_type* _left;
	node_type* _right;
	node_type* _FatherNode;  
	Color _color;

	RBTreeNode(const K& key)
		:_ky(key)
		,_left(nullptr)
		,_right(nullptr)
		, _FatherNode(nullptr)  
		,_color(RED)
	{
	}

};

template <class K> 
class RBTree
{
public:

	typedef RBTreeNode <K> node_type;

	RBTree()
		:_root(nullptr)    
	{
	}

	bool Insert(const K& key)//插入元素     
	{
		//
		//

		if (nullptr == _root) //是否是空树
		{
			_root = new node_type(key);
			_root->_color = BLACK;     
			return true;
		
		}
		//
		node_type* cur = _root;
		node_type* fathernode = nullptr;  
		
		while (cur)
		{
			if (cur->_ky < key)    
			{
				fathernode = cur;  
				cur = cur->_right;
			}
			else if (cur->_ky > key)     
			{
				fathernode = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}

		}


		cur = new node_type(key); //新插入节点 
		if (fathernode->_ky < cur->_ky)    
		{
			fathernode->_right = cur;
			cur->_FatherNode = fathernode;

		}
		else
		{
			fathernode->_left = cur;
			cur->_FatherNode = fathernode;
		}


		while (fathernode && fathernode->_color == RED)  
		{
			node_type* grandfather_node = fathernode->_FatherNode;
			node_type* unclenode = nullptr;

			if (fathernode == grandfather_node->_left) //父亲节点是祖父节点的左孩子
			{
				 unclenode = grandfather_node->_right; //叔叔节点是祖父节点的右孩子

				 if (unclenode == nullptr || unclenode->_color == BLACK)
				 {
					 if (cur == fathernode->_left)  
					 {
						 RevolveRight(fathernode);
						 fathernode->_color = BLACK;
						 grandfather_node->_color = RED; 
					 }
					 else if (cur == fathernode->_right)
					 {
						 RevolveLeft(fathernode);
						 RevolveRight(cur);

						 cur->_color = BLACK;   
						 grandfather_node->_color = RED;     
					 }
				 }
		        else
				{
					fathernode->_color = BLACK; //父亲变黑
					unclenode->_color = BLACK;  //叔叔变黑
					grandfather_node->_color = RED; //爷爷变红
					 
					cur = grandfather_node;
					fathernode = cur->_FatherNode; 
					
				}

			}
			else//父亲节点是祖父节点的右孩子
			{
				unclenode = grandfather_node->_left; //叔叔节点是祖父节点的左孩子       

				if (unclenode == nullptr || unclenode->_color == BLACK)
				{
					if (cur == fathernode->_right)
					{
						RevolveLeft(fathernode);   
						fathernode->_color = BLACK;
						grandfather_node->_color = RED;
					}
					else if (cur == fathernode->_left)
					{
						RevolveRight(fathernode);
						RevolveLeft(cur);     
						
						cur->_color = BLACK;
						grandfather_node->_color = RED;
					}
				}
				else  
				{
					fathernode->_color = BLACK; //父亲变黑
					unclenode->_color = BLACK;  //叔叔变黑
					grandfather_node->_color = RED; //爷爷变红

					cur = grandfather_node;
					fathernode = cur->_FatherNode;
				
				}
			}
			
		  



	
		}


		_root->_color = BLACK;  
		return true;

	}







private:
	void RevolveLeft(node_type*& fathernode)//左单旋        
	{
		node_type* cur = fathernode->_right;  

		fathernode->_right = cur->_left;

		if (cur->_left != nullptr)
			cur->_left->_FatherNode = fathernode;

		cur->_FatherNode = fathernode->_FatherNode;

		if (fathernode->_FatherNode != nullptr)
		{
			if (fathernode->_FatherNode->_right == fathernode)
			{
				fathernode->_FatherNode->_right = cur;
			}
			else
			{
				fathernode->_FatherNode->_left = cur;
			}
		}

		cur->_left = fathernode;
		fathernode->_FatherNode = cur;

	}



	void RevolveRight(node_type*& fathernode) //右单旋 
	{
		node_type* cur = fathernode->_left;  

		fathernode->_left = cur->_right;

		if (cur->_right != nullptr)
			cur->_right->_FatherNode = fathernode;

		cur->_FatherNode = fathernode->_FatherNode;

		if (fathernode->_FatherNode != nullptr)
		{
			if (fathernode->_FatherNode->_right == fathernode)
			{
				fathernode->_FatherNode->_right = cur;
			}
			else
			{
				fathernode->_FatherNode->_left = cur;
			}
		}

		cur->_right = fathernode;
		fathernode->_FatherNode = cur;

	}



	node_type* _root;
};

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

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

相关文章

GitHub打不开的解决方案

1、打开https://sites.ipaddress.com/github.com/找到DNS Resource Records&#xff0c;复制github的ip地址&#xff0c;先保存起来&#xff1a; 140.82.112.32、打开https://sites.ipaddress.com/fastly.net/找到DNS Resource Records&#xff0c;复制其中一个ip地址&#xf…

ComfyUI 高级实战:极速稳定视频风格转绘

大家好&#xff0c;我是每天分享AI应用的萤火君&#xff01; 重绘视频一直是短视频平台上的热点内容&#xff0c;流量不错。重绘视频一般是将真实视频重绘为动漫风格&#xff0c;或者是使用新的人物形象重放视频中的人物动作&#xff0c;再或者只是重绘视频中的部分内容&#…

Android 音视频从入门到提高 -- 任务列表——task1

1.在 Android 平台绘制一张图片&#xff0c;使用至少3种不同的 APl&#xff0c;lmageView&#xff0c;SurfaceView&#xff0c;自定义 Vew 布局xml <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.a…

通配符证书和多域名证书主要区别以及如何选择

一、通配符证书与多域名证书的主要区别 1.覆盖域名范围 通配符证书可以保护一个主域名及其所有二级子域名&#xff0c;并对该级子域名数量无限制。这种特性使得通配符证书在拥有大量子域名的网站上非常受欢迎&#xff0c;可以大大简化证书管理和维护工作。 多域名证书允许在…

基于ChatGPT+RPA的融资融券业务担保资产风险评价

原载《会计之友》2024年第2期 作者简介 李闻一 男&#xff0c;湖北洪湖人&#xff0c;华中师范大学经济与工商管理学院教授、博士生导师&#xff0c;会计学科带头人&#xff0c;研究方向&#xff1a;财务共享、公司金融、风险管理 黄怡凡 女&#xff0c;湖北公安人&#xf…

2024中青杯数学建模竞赛A题人工智能视域下养老辅助系统的构建思路代码论文分析

2024中青杯数学建模A题论文和代码已完成&#xff0c;代码为A题全部问题的代码&#xff0c;论文包括摘要、问题重述、问题分析、模型假设、符号说明、模型的建立和求解&#xff08;问题1模型的建立和求解、问题2模型的建立和求解、问题3模型的建立和求解&#xff09;、模型的评价…

Appium系列(2)元素定位工具appium-inspector

背景 如实现移动端自动化&#xff0c;依赖任何工具时&#xff0c;都需要针对于页面中的元素进行识别&#xff0c;通过识别到指定的元素&#xff0c;对元素进行事件操作。 识别元素的工具为appium官网提供的appium-inspector。 appium-inspector下载地址 我这里是mac电脑需要下…

C#子窗体嵌入主窗体

上位机开发中&#xff0c;经常会需要将子窗体嵌入到主窗体。 运行结果 核心实现&#xff1a; private void button2_Click(object sender, EventArgs e){Form3 childForm new Form3();//判断容器中是否已经打开子窗体&#xff0c;如果打开现将其关闭foreach (Control item in…

【C++】Vector的简易模拟与探索

&#x1f49e;&#x1f49e; 前言 hello hello~ &#xff0c;这里是大耳朵土土垚~&#x1f496;&#x1f496; &#xff0c;欢迎大家点赞&#x1f973;&#x1f973;关注&#x1f4a5;&#x1f4a5;收藏&#x1f339;&#x1f339;&#x1f339; &#x1f4a5;个人主页&#x…

并查集详解及实现

看完这篇文章你将会知道&#xff1a; 什么是并查集&#xff1f; 并查集的原理。 并查集的JAVA实现。 并查集这部分内容还是很简单的&#xff0c;相信只要认真学&#xff0c;你正在上小学二年级的表弟都能学得会。(▽ʃ♡ƪ) 目录 一、啥是并查集&#xff1f; 二、并查集的…

什么样的展馆场馆才是科技满满?就差一张智慧场馆大屏

随着科技的飞速发展&#xff0c;传统的场馆展示方式已经无法满足现代人对信息获取和体验的需求。智慧场馆大屏作为一种新型的展示方式&#xff0c;应运而生。它将高清大屏显示技术、智能交互技术、数据分析技术等融为一体&#xff0c;为观众带来更加丰富、生动的展示体验。 一…

跨境热销爆款货源哪里找?选品工具不能少

通常&#xff0c;跨境电商找热销货源的几种方法&#xff1a; 1、使用Google Trends、亚马逊销售排行等来追踪和分析当前的市场趋势和热门产品&#xff1b; 2、关注社交媒体、行业论坛和博客等渠道&#xff0c;以获取最新的市场信息和消费者反馈&#xff1b; 3、在主流的跨境…

python-编写函数判断一个三位数是否为水仙花数。

【问题描述】要求编写函数isflower(n)判断一个三位数n是否为水仙花数,如果是&#xff0c;则返回True&#xff0c;否则返回False。在主程序中要求调用该函数并输出三位数中所有的水仙花数。所谓"水仙花数"是指一个3位数&#xff0c;其各位数字立方和等于该数本身。例如…

【动态规划】零基础解决路径问题(C++)

目录 62.路径问题 解法&#xff08;动态规划&#xff09;&#xff1a; 1. 状态表⽰&#xff1a; 2. 状态转移⽅程&#xff1a; 3. 初始化&#xff1a; 4. 填表顺序&#xff1a; 5. 返回值&#xff1a; 不同路径2.0 解法&#xff08;动态规划&#xff09;&#xff1a; …

MySQL——存储过程,触发器

BaiduComate: # 问题1&#xff1a; # 问题1&#xff1a; 帮我创建两个表student与score表&#xff0c;要求student表有id&#xff0c;createDate&#xff0c;userName&#xff0c;phone&#xff0c;age&#xff0c;sex&#xff0c;introduce&#xff0c; 要求score表有id&…

迷你手持小风扇哪个牌子质量好点?这五款迷你手持小风扇不要错过

随着空调的普及&#xff0c;我们对夏日热浪的抵抗力逐渐减弱。当从凉爽的空调屋步入闷热的户外、拥挤的交通工具或公共场所时&#xff0c;如何抵御热浪的侵袭成为大众关注的焦点。在这样的背景下&#xff0c;迷你手持小风扇凭借其便携性和即时降温功能&#xff0c;成为众多人的…

和可被k整除的子数组 ---- 前缀和

题目链接 题目: 分析: 补充知识 1. 同余定理: (a-b) % p 0即a-b能被p整除, > a % p b % p 2. c, java中 [负数 % 正数] 的结果是负数, 想要得到正确结果 > (a%pp)%p这道题和<和为k的子数组>类似, 利用前缀和的思想, 计算以i结尾的所有子数组, 前缀和为sum[i] …

炸裂!AI五分钟模仿爆款IP故事,涨粉速度太绝了!

‍ ‍大家好&#xff0c;我是向阳。 今天我要分享一个利用AI技术模仿爆款账号的小技巧&#xff0c;帮助大家迅速增加粉丝。这个方法简单实用&#xff0c;尤其适用于副业和本地生活领域。接下来&#xff0c;我将为大家详细讲解操作步骤。让我们开始吧。 副业赚钱&#xff1a;模…

QT 程序缺少API-开头文件

无法启动此程序&#xff0c;因为计算机中丢失api-ms-win-core-rtlsupport-11-2-0dl。尝试重新安装该程序以解决此问题 因为打包QT程序经常去到的别的电脑会缺少系统的MFC开头 msvcp 的库所以&#xff0c;QT自带的VC安装包体验不够好所以都是自己本地复制一套进行打包。。 有部…

LVDS与IDELAY

摘要&#xff1a;LVDS&#xff08;Low-Voltage Differential Signaling&#xff09;低电压差分信号&#xff0c;是一种低功耗、低误码率、低串扰和低辐射的差分信号技术&#xff1b;LVDS会被经常使用到&#xff0c;使用的过程中难免会碰到时序问题&#xff0c;需要借助IDELAY进…