【C++笔记】红黑树(RBTree)深度剖析和AVL树的对比分析

news2025/1/6 12:37:06

【C++笔记】红黑树(RBTree)深度剖析和AVL树的对比分析

在这里插入图片描述

🔥个人主页大白的编程日记

🔥专栏C++笔记


文章目录

  • 【C++笔记】红黑树(RBTree)深度剖析和AVL树的对比分析
    • 前言
    • 一.红黑树的定义
      • 1.1 红黑树的概念
      • 1.2红黑树的规则
      • 1.3 红黑树对比AVL树
    • 二.红黑树的插入
      • 2.1插入分析
      • 2.1变色
      • 2.2 旋转+变色
      • 2.3 代码实现
    • 三.红黑树的验证
      • 3.1 思路分析
      • 3.2 代码实现
    • 后言

前言

哈喽,各位小伙伴大家好!上期我们讲了反向迭代器和计算器。今天我们来讲一下红黑树的深度剖析。话不多说,我们进入正题!向大厂冲锋
在这里插入图片描述

一.红黑树的定义

1.1 红黑树的概念

红黑树是⼀棵⼆叉搜索树,他的每个结点增加⼀个存储位来表示结点的颜色,可以是红色或者黑色。通过对任何⼀条从根到叶子的路径上各个结点的颜色进行约束,红黑树确保没有⼀条路径会比其他路径长出2倍,因而是接近平衡的。

1.2红黑树的规则

  • 每个结点不是红色就是黑色

  • 根节点是黑色的

  • 如果⼀个结点是红色的,则它的两个孩子结点必须是黑色的,也就是说任意⼀条路径不会有连续的红色结点。

  • 对于任意⼀个结点,从该结点到其所有NULL结点的简单路径上,均包含相同数量的黑色结点
    这些都是红黑树。

  • 说明:《算法导论》等书籍上补充了⼀条每个叶⼦结点(NIL)都是黑色的规则。他这里所指的叶子结点不是传统的意义上的叶子结点,而是我们说的空结点,有些书籍上也把NIL叫做外部结点。NIL是为了方便准确的标识出所有路径,《算法导论》在后续讲解实现的细节中也忽略了NIL结点,所以我们知道⼀下这个概念即可。
    =
    这棵树我们看可能以为是四条路径。

    但是其实他是八条路径。
    规定空节点是黑的符合红黑树的规则并且方便我们观察红黑树的路径。

由中这几点规则我们可以推出红黑树的性质:

  • 最长路径<=2*最短路径

1.3 红黑树对比AVL树

红黑树的性能不如AVL树因为他没有那么严格地控制高度差。但是他的性能也不会太差,因为他控制最长路径<=2*最短路径。所以他的性能还是在logN的量级

红黑树的表达相对AVL树要抽象⼀些,AVL树通过高度差直观的控制了平衡。红黑树通过4条规则的颜色约束,间接的实现了近似平衡,他们效率都是同⼀档次,但是相对而言,插入相同数量的结点,红黑树的旋转次数是更少的,因为他对平衡的控制没那么严格。

在这**加粗样式**里插入图片描述
总结:

  • 查找
    红黑树的性能稍逊一筹AVL树
    因为他的高度控制没那么严格但都是logN量级。

  • 插入
    红黑树的性能要比AVL树好
    虽然红黑树多了变色操作,但是变色操作简单,代价小。 并且红黑树高度控制没那么严格大量插入时旋转的调整次数比AVL树要少。所以插入是红黑树的性能更优秀。

二.红黑树的插入

2.1插入分析

  • 插入⼀个值按⼆叉搜索树规则进行插入,插入后我们只需要观察是否符合红黑树的4条规则。
  • 如果是空树插入,新增结点是黑色结点。如果是非空树插⼊,新增结点必须红色结点,因为非空树插入,新增黑色结点就破坏了规则4,规则4是很难维护的。
  • 非空树插入后,新增结点必须红色结点,如果父亲结点是黑色的,则没有违反任何规则,插⼊结束
  • 非空树插入后,新增结点必须红色结点,如果⽗亲结点是红⾊的,则违反规则3。进⼀步分析,c是红色,p为红,g必为黑,这三个颜色都固定了,关键的变化看u的情况,需要根据u分为以下几种情况分别处理。
    说明:下图中假设我们把新增结点标识为c (cur),c的父亲标识为p(parent),p的父亲标识为g(grandfather),p的兄弟标识为u(uncle)。

2.1变色

c为红,p为红,g为黑,u存在且为红,则将p和u变黑,g变红。在把g当做新的c,继续往上更新。

分析:因为p和u都是红色,g是黑色,把p和u变黑,左边子树路径各增加⼀个黑色结点,g再变红,相当于保持g所在子树的黑色结点的数量不变,同时解决了c和p连续红色结点的问题,需要继续往上更新是因为,g是红色,如果g的父亲还是红色,那么就还需要继续处理;如果g的父亲是黑色,则处理结束了;如果g就是整棵树的根,再把g变回黑色。

p是右子树也是一样的

所以p和cur在左还是在右我们并不关注。
只要u存在并且为红色我们都按照这种方式处理即可。

2.2 旋转+变色

如果u不存在或u存在为黑。
我们需要根据cur和p的位置分类讨论

  • 单旋

c为红,p为红,g为黑,u不存在或者u存在且为黑,u不存在,则c⼀定是新增结点,u存在且为黑,则c⼀定不是新增,c之前是黑色的,是在c的子树中插入,,变色将c从黑色变成红色,更新上来的。

分析:p必须变黑色,才能解决,连续红色结点的问题,u不存在或者是黑色的,这里单纯的变色无法解决问题,需要旋转+变色。

如果p是左子树并cur是左子树
在这里插入图片描述

如果p是g的左,c是p的左,那么以g为旋转点进行右单旋,再把p变黑,g变红即可。p变成课这颗树新的根,这样子树黑色结点的数量不变,没有连续的红色结点了,且不需要往上更新,因为p的父亲是黑色还是红色或者空都不违反规则。

如果p是右子树并cur是右子树

如果p是g的右,c是p的右,那么以g为旋转点进行左单旋,再把p变黑,g变红即可。p变成课这颗树新的根,这样i树黑色结点的数量不变,没有连续的红结点了,且不需要往上更新,因为p的父亲是黑色还是红色或者空都不违反规则。

那就是以g进行左单旋 在把g变红 p变黑。

  • 双旋

如果p是左子树cur是右子树

如果p是g的左,c是p的右,那么先以p为旋转点进行左单旋,再以g为旋转点进行右单旋,再把c变黑,g变红即可。c变成课这颗树新的根,这样子树黑色结点的数量不变,没有连续的红色结点了,且不需要往上更新,因为c的父亲是黑色还是红色或者空都不违反规则。

如果p是右子树cur是左子树

如果p是g的右,c是p的左,那么先以p为旋转点进行右单旋,再以g为旋转点进行左单旋,再把c变黑,g变红即可。c变成课这颗树新的根,这样子树黑色结点的数量不变,没有连续的红色结点了,且不需要往上更新,因为c的父亲是黑色还是红色或者空都不违反规则。

2.3 代码实现

bool Insert(const k& x, const v& v)
{
	if (_root == nullptr)//插入根节点
	{
		_root = new node(x, v);
		return true;
	}
	node* cur = _root;
	node* parent = nullptr;//保留父亲节点
	while (cur)
	{
		/*介质不冗余*/
		if (x < cur->_key)
		{
			parent = cur;
			cur = cur->left;
		}
		else if (x > cur->_key)
		{
			parent = cur;
			cur = cur->right;
		}
		else
		{
			return false;
		}
	}
	cur = new node(x, v);
	cur->col = RED;
	if (x > parent->_key)
	{
		parent->right = cur;
	}
	else//相等插入左子树
	{
		parent->left = cur;
	}
	cur->parent = parent;
	while (parent&&parent->parent&&parent->col == RED)
	{
		node* grandfather = parent->parent;
		if (parent == grandfather->left)
		{
			node* uncle = grandfather->right;
			//         g
		    //       p    u
		    //     c  c
			//u存在且为红
			if (uncle&&uncle->col==RED)
			{
				parent->col = uncle->col=BLACK;
				grandfather->col = RED;
				cur = grandfather;
				parent = cur->parent;
			}
			//u不存在或存在为黑
			else
			{
				//         g
				//       p    
				//     c
				if (cur == parent->left)
				{
					RoRateR(grandfather);
					parent->col = BLACK;
					grandfather->col = RED;
				}
				//         g
				//       p    
				//         c
				else
				{
					RoRateL(parent);
					RoRateR(grandfather);
					cur->col = BLACK;
					grandfather->col = RED;
				}
				break;
			}
		}
		else
		{
			node* uncle = grandfather->left;
			//         g
			//       u   p    
			//          c  c
			//u存在且为红
			if (uncle && uncle->col == RED)
			{
				parent->col = uncle->col = BLACK;
				grandfather->col = RED;
				cur = grandfather;
				parent = cur->parent;
			}
			//u不存在或存在为黑
			else
			{
				//         g
				//            p    
				//              c
				if (cur == parent->right)
				{
					RoRateL(grandfather);
					parent->col = BLACK;
					grandfather->col = RED;
				}
				//         g
				//            p    
				//          c
				else
				{
					RoRateR(parent);
					RoRateL(grandfather);
					cur->col = BLACK;
					grandfather->col = RED;
				}
				break;
			}
		}
	}
	_root->col = BLACK;//循环结束把根变黑
	return true;
}
	

三.红黑树的验证

3.1 思路分析

我们检查红黑树主要检查是否满足红黑树的规则即可。

3.2 代码实现


bool check(node* cur,size_t tmp,size_t sum)
{
	if (cur == nullptr)
	{
		if (tmp != sum)
		{
			cout << "存在⿊⾊结点的数量不相等的路径" << endl;
			return false;
		}
		return true;
	}
	if (cur->col == RED)
	{
		if (cur->parent->col == RED)
		{
			cout << cur->_key << ":" << "存在连续红节点" << endl;
			return false;
		}
	}
	else
	{
		sum++;
	}
	return check(cur->left, tmp, sum) && check(cur->right, tmp, sum);
}
bool ISRBTree()
{
	 return _ISRBTree(_root);
}
bool _ISRBTree(node* cur)
{
	if (cur == nullptr)
	{
		return true;
	}
	if (cur->col == RED)
	{
		return false;
	}
	size_t t = 0;
	while (cur)
	{
		if (cur->col == BLACK)
		{
			t++;
		}
		cur = cur->left;
	}
	return check(_root,t,0);
}

这里我们通过几组数据来看一下AVL树和红黑树的性能。

void Test()
{
	const int N = 100000;
	vector<int> v;
	v.reserve(N);
	srand(time(0));
	for (size_t i = 0; i < N; i++)
	{
		v.push_back(rand() + i);
	}
	size_t begin2 = clock();
	AVL::AVLTree<int, int> t;
	for (auto e : v)
	{
		t.Insert(e, e);
	}
	size_t end2 = clock();
	size_t begin3 = clock();
	RBTree::RBTree<int, int> t1;
	for (auto e : v)
	{
		t1.Insert(e, e);
	}
	size_t end3 = clock();
	size_t begin1 = clock();
	// 确定在的值
	/*for (auto e : v)
	{
	t.Find(e);
	}*/
	// 随机值
	for (size_t i = 0; i < N; i++)
	{
		t.Find((rand() + i));
	}
	size_t end1 = clock();
	size_t begin4 = clock();
	for (size_t i = 0; i < N; i++)
	{
		t1.Find((rand() + i));
	}
	size_t end4 = clock();
	cout << "AVLInsert:" << end2 - begin2 << endl;
	cout << "AVLTree:"<<t.IsBalanceTree() << endl;
	cout << "AVLHeight:" << t.Height() << endl;
	cout << "AVLSize:" << t.Size() << endl;
	cout << "AVLFind:" << end1 - begin1 << endl;
	cout << "RBInsert:" << end3 - begin3 << endl;
	cout << "RBTree:" << t1.ISRBTree() << endl;
	cout << "RBHeight:" << t1.Height() << endl;
	cout << "RBSize:" << t1.Size() << endl;
	cout << "RBFind:" << end4 - begin4 << endl;
}
  • Debug
    10万数据
    在这里插入图片描述100万数据

    1000万数据
    在这里插入图片描述
  • Release
    10万数据

    100万数据

    1000万数据
    在这里插入图片描述

观察数据可以发现红黑树的查找比AVL要慢一些,
在release下查找效率都一样,因为CPU太快了。
但是红黑树在大量数据插入的情况下比AVL树要不少。

后言

这就是红黑树的深度剖析。大家自己好好消化!今天就分享到这!感谢各位的耐心垂阅!咱们下期见!拜拜~

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

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

相关文章

grouped.get_group((‘B‘, ‘A‘))选择分组

1. df.groupby([team, df.name.str[0]]) df.groupby([team, df.name.str[0]]) 这一部分代码表示对 DataFrame df 按照 两个条件 进行分组&#xff1a; 按照 team 列&#xff08;即团队&#xff09;。按照 name 列的 首字母&#xff08;df.name.str[0]&#xff09;。 df.name.s…

计算机毕设-基于springboot的食品厂管理系统的设计与实现(附源码+lw+ppt+开题报告)

博主介绍&#xff1a;✌多个项目实战经验、多个大型网购商城开发经验、在某机构指导学员上千名、专注于本行业领域✌ 技术范围&#xff1a;Java实战项目、Python实战项目、微信小程序/安卓实战项目、爬虫大数据实战项目、Nodejs实战项目、PHP实战项目、.NET实战项目、Golang实战…

【Ubuntu20.04】Apollo10.0 Docker容器部署+常见错误解决

官方参考文档【点击我】 Apollo 10.0 版本开始&#xff0c;支持本机和Docker容器两种部署方式。 如果您使用本机部署方式&#xff0c;建议使用x86_64架构的Ubuntu 22.04操作系统或者aarch64架构的Ubuntu 20.04操作系统。 如果您使用Docker容器部署方式&#xff0c;可以使用x…

Java项目实战II基于小程序的驾校管理系统(开发文档+数据库+源码)

目录 一、前言 二、技术介绍 三、系统实现 四、核心代码 五、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。 一、前言 随着汽车保有量的不断增长&#xff0c;驾驶培训市场日…

基于STM32F103的USART的原理及应用(一)(实现手机BLE和MCU进行通信)

一&#xff0c;查阅数据手册&#xff0c;找到对应的IO口和外设总线并配置好外设源文件 想了解USART的具体原理的小伙伴请进传送门&#xff1a;&#xff08;总结&#xff09;STM32中USART原理及应用&#xff08;PC、BLE、ESP8266通信实现&#xff09;-CSDN博客 二&#xff0c;打…

C#实现画图,及实现图像运动,C#中GDI+图形图像技术(Graphics类、Pen类、Brush类)C#之快速入门GDI+绘图 C#实现快速画图功能

下载源码 <-------- 在C#的世界里&#xff0c;GDI如同一位多才多艺的艺术家&#xff0c;以其强大的绘图能力&#xff0c;让开发者能够轻松地在应用程序中挥洒创意&#xff0c;绘制出丰富多彩的图形世界。GDI不仅支持基本的几何图形绘制&#xff0c;还能处理复杂的图像处理任…

Python应用指南:高德交通态势数据

在现代城市的脉络中&#xff0c;交通流量如同流动的血液&#xff0c;交通流量的动态变化对出行规划和城市管理提出了更高的要求。为了应对这一挑战&#xff0c;高德地图推出了交通态势查询API&#xff0c;旨在为开发者提供一个强大的工具&#xff0c;用于实时获取指定区域或道路…

数据结构与算法Python版 图的应用与广度优先搜索

文章目录 一、图的应用-词梯问题二、图的广度优先搜索 一、图的应用-词梯问题 词梯问题 Word Ladder 从一个单词演变到另一个单词&#xff0c;其中的过程可以经过多个中间单词。要求是相邻两个单词之间差异只能是1个字母如FOOL变SAGE&#xff1a;FOOL >> POOL >>…

服务器数据恢复—服务器硬盘亮黄灯的数据恢复案例

服务器硬盘指示灯闪烁黄灯是一种警示&#xff0c;意味着服务器硬盘出现故障即将下线。发现这种情况建议及时更换硬盘。 一旦服务器上有大量数据频繁读写&#xff0c;硬盘指示灯会快速闪烁。服务器上某个硬盘的指示灯只有黄灯亮着&#xff0c;而其他颜色的灯没有亮的话&#xff…

Java SpringBoot使用EasyExcel导入导出Excel文件

点击下载《Java SpringBoot使用EasyExcel导入导出Excel文件(源代码)》 在 Java Spring Boot 项目中&#xff0c;导入&#xff08;读取&#xff09;和导出&#xff08;写入&#xff09; Excel 文件是一项常见的需求。EasyExcel 是阿里巴巴开源的一个用于简化 Java 环境下 Excel…

Deduction(演绎法)和Reduction(还原法)-关于中西方思维的差异

Deduction(演绎法)和Reduction(还原法)-关于中西方思维的差异 最近看到中国新一代战机上天的消息,感慨万千;忽然想起来两年多前一次爬山的时候,一个友人跟我大概说过,Deduction和Reduction分别对应了中国古代和西方古代以来的思考自然和技术发明的思想.于是又在这方面琢磨了一番…

unity学习6:unity的3D项目的基本操作

目录 1 unity界面的基本认识 1.1 file 文件 1.2 edit 编辑/操作 1.3 Assets 1.4 gameobject 游戏对象 1.5 组件 1.6 windows 2 这些部分之间的关系 2.1 关联1&#xff1a; Assets & Project 2.2 关联2&#xff1a;gameobject & component 2.3 关联3&#xff…

【银河麒麟高级服务器操作系统实例】tcp半链接数溢出分析及处理全过程

了解更多银河麒麟操作系统全新产品&#xff0c;请点击访问 麒麟软件产品专区&#xff1a;https://product.kylinos.cn 开发者专区&#xff1a;https://developer.kylinos.cn 文档中心&#xff1a;https://document.kylinos.cn 服务器环境以及配置 系统环境 物理机/虚拟机/云…

k8s基础(2)—Kubernetes-Namespace

一、Namespace概述 名字空间 在 Kubernetes 中&#xff0c;名字空间&#xff08;Namespace&#xff09; 提供一种机制&#xff0c;将同一集群中的资源划分为相互隔离的组。 同一名字空间内的资源名称要唯一&#xff0c;但跨名字空间时没有这个要求。 名字空间作用域仅针对带有…

.NET框架用C#实现PDF转HTML

HTML作为一种开放标准的网页标记语言&#xff0c;具有跨平台、易于浏览和搜索引擎友好的特性&#xff0c;使得内容能够在多种设备上轻松访问并优化了在线分享与互动。通过将PDF文件转换为HTML格式&#xff0c;我们可以更方便地在浏览器中展示PDF文档内容&#xff0c;同时也更容…

医学图像分析工具01:FreeSurfer || Recon -all 全流程MRI皮质表面重建

FreeSurfer是什么 FreeSurfer 是一个功能强大的神经影像学分析软件包&#xff0c;广泛用于处理和可视化大脑的横断面和纵向研究数据。该软件由马萨诸塞州总医院的Martinos生物医学成像中心的计算神经影像实验室开发&#xff0c;旨在为神经科学研究人员提供一个高效、精确的数据…

JavaScript 基础2

js的运算符 算数运算符 相加求和&#xff0c;如果用在字符串则是拼接 -相减求差 *相乘求积 /相除求商 %模除求余 具体用法如下 let num 154 let num2 15 document.write(numnum2) document.write(<br>) document.write(num-num2) document.write(<br>) do…

Leecode刷题C语言之我的日程安排表②

执行结果:通过 执行用时和内存消耗如下&#xff1a; typedef struct {int start;int end; }BOOKING;#define MAX_BOOK_NUM (1000) typedef struct MyCalendar_ {BOOKING book[MAX_BOOK_NUM];int bnum;BOOKING *sorted[MAX_BOOK_NUM];int num;int conflict[MAX_BOOK_NUM];int c…

【C语言的小角落】--- 深度理解取余/取模运算

Welcome to 9ilks Code World (๑•́ ₃ •̀๑) 个人主页: 9ilk (๑•́ ₃ •̀๑) 文章专栏&#xff1a; C语言的小角落 本篇博客我们来深度理解取余/取模&#xff0c;以及它们在不同语言中出现不同现象的原因。 &#x1f3e0; 关于取整 &#x1f3b5; 向0取整…

网关的主要类型和它们的特点

网关&#xff0c;作为网络通信的关键节点&#xff0c;根据其应用场景和功能特点&#xff0c;可以分为多种类型。 1.协议网关 特点&#xff1a; • 协议转换&#xff1a;协议网关的核心功能是转换不同网络之间的通信协议。例如&#xff0c;它可以将IPv4协议的数据包转换为IPv6协…