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

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

在这里插入图片描述

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

🔥专栏C++笔记


文章目录

  • 【C++笔记】红黑树(RBTree)深度剖析和AVL树的对比分析
    • 前言
    • 一.红黑树的定义
      • 1.1 红黑树的概念
      • 1.2红黑树的规则
      • 1.3 红黑树对比AVL树
    • 二.红黑树的插入
      • 2.1插入分析
      • 2.1变色
      • 2.2 旋转+变色
      • 2.3 代码实现
    • 三.红黑树的查找
    • 四.红黑树的验证
      • 4.1 思路分析
      • 4.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;
}
	

三.红黑树的查找

红黑树的查找和AVL树的规则一样。
对比根节点和目标值的大小再取左子树或右子树查找。

node* Find(const k& x)
{
	node* ret = nullptr;
	node* cur = _root;
	while (cur)
	{
		if (x < cur->_key)
		{
			cur = cur->left;
		}
		else if (x > cur->_key)
		{
			cur = cur->right;
		}
		else
		{
			ret = cur;//保留当前节点
			cur = cur->left;//继续向左子树查找中序的第一个
		}
	}
	return ret;
}

四.红黑树的验证

4.1 思路分析

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

4.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树要不少。

后言

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

(概率论)无偏估计

参考文章&#xff1a;(15 封私信 / 51 条消息) 什么是无偏估计&#xff1f; - 知乎 (zhihu.com) 首先&#xff0c;第一个回答中&#xff0c;马同学图解数学讲解得很形象&#xff0c; 我的概括是&#xff1a;“注意&#xff0c;有一个总体的均值u。然后&#xff0c;如果抽样n个&…

Mac中配置vscode(第一期:python开发)

1、终端中安装 xcode-select --install #mac的终端中安装该开发工具 xcode-select -p #显示当前 Xcode 命令行工具的安装路径注意&#xff1a;xcode-select --install是在 macOS 上安装命令行开发工具(Command Line Tools)的关键命令。安装的主要组件包括&#xff1a;C/C 编…

【顶刊TPAMI 2025】多头编码(MHE)之极限分类 Part 3:算法实现

目录 1 三种多头编码&#xff08;MHE&#xff09;实现1.1 多头乘积&#xff08;MHP&#xff09;1.2 多头级联&#xff08;MHC&#xff09;1.3 多头采样&#xff08;MHS&#xff09;1.4 标签分解策略 论文&#xff1a;Multi-Head Encoding for Extreme Label Classification 作者…

【形式篇】年终总结怎么写:PPT如何将内容更好地表现出来

——细节满满&#xff0c;看完立马写出一篇合格的PPT 总述 形式服务于内容&#xff0c;同时合理的形式可以更好地表达和彰显内容 年终总结作为汇报型PPT&#xff0c;内容一定是第一位的&#xff0c;在内容篇(可点击查看)已经很详细地给出了提纲思路&#xff0c;那如何落实到…

软件项目体系建设文档,项目开发实施运维,审计,安全体系建设,验收交付,售前资料(word原件)

软件系统实施标准化流程设计至关重要&#xff0c;因为它能确保开发、测试、部署及维护等各阶段高效有序进行。标准化流程能减少人为错误&#xff0c;提升代码质量和系统稳定性。同时&#xff0c;它促进了团队成员间的沟通与协作&#xff0c;确保项目按时交付。此外&#xff0c;…

大模型(LLM) 的长上下文与 RAG:评估与回顾

大模型的长上下文与 RAG 以下是本文的主要发现&#xff1a; 在问答基准测试中&#xff0c;LC 的表现通常优于 RAG 基于摘要的检索与 LC 性能相当&#xff0c;而基于块的检索则落后 RAG 在基于对话和一般性问题查询方面具有优势 本文对结果进行了深入分析&#xff0c;请查看。 …

SSR 【1】【nuxt安装】

文章目录 前言如何解决 前言 nuxt提供了nuxi脚手架工具&#xff0c;让开发者便捷生成nuxt模板项目。nuxt官网 npx nuxilatest init <project-name>但是几乎大部分的人在安的时候都会遇到这个问题 如何解决 在C:\Windows\System32\drivers\etc\hosts中增加如下解析记录…

性能测试05|JMeter:分布式、报告、并发数计算、性能监控

目录 一、JMeter分布式 1、应用场景 2、原理 3、分布式相关注意事项 4、分布式配置与运行 二、JMeter报告 1、聚合报告 2、HTML报告 三、并发用户数&#xff08;线程数&#xff09;计算 四、JMeter下载第三方插件 五、性能监控 1、Concurrency Thread Group 线程组…

CURSOR 应用:深入理解字符前缀条件算法(Character Prefix Conditioning)

前言 在代码补全中&#xff0c;用户期待智能模型能根据输入快速、准确地给出建议。但现代语言模型基于Token序列运作&#xff0c;这在处理非Token边界输入时会带来偏差。为了解决这一问题&#xff0c;本文将探讨一种高效算法——字符前缀条件算法&#xff08;Character Prefix…

滤波器设计流程

sos滤波器是什么为什么要 zpk2sos如何实现零相位滤波&#xff0c;优缺点分别是什么 滤波器的计算流程 滤波器的计算设计流程&#xff1a; 1.输入验证和处理&#xff1a; 2.检查频率范围是否合法&#xff0c;计算归一化的频率。 3.滤波器设计&#xff1a;设计带通 Butterworth…

【游戏设计原理】53 - 解决问题的障碍

1. 分析并总结原理 核心观点 游戏本质是一系列问题解决的过程&#xff0c;通过设计巧妙的问题和决策场景&#xff0c;游戏能激发玩家的兴趣和投入感。然而&#xff0c;当问题解决的过程被阻碍时&#xff0c;会降低玩家的体验甚至让他们放弃游戏。文中提到的四种障碍反映了玩家…

【多线程初阶篇¹】线程理解| 线程和进程的区别

目录 一、认识线程Thread 1.为啥引入线程 2.线程理解 &#x1f525; 3.面试题&#xff1a;线程和进程的区别 一、认识线程Thread 1.为啥引入线程 为了解决进程太重量的问题 解释&#xff08;为什么说线程比进程更轻量&#xff1f;/为什么说线程创建/销毁开销比进程小&#…

平面坐标转大地坐标(arcgisPro中进行)

1、将需要转换的红线导入arcgisPro中&#xff0c;如下&#xff1a; 2、在地图菜单栏中&#xff0c;选择坐标转换工具&#xff0c;如下&#xff1a; 3、打开坐标转换工具 4、开启捕捉 5、 设置大地坐标显示格式 6、如下&#xff1a; 7、显示如图&#xff1a; 8、再依次添加几个待…

CentOS: RPM安装、YUM安装、编译安装(详细解释+实例分析!!!)

目录 1.什么是RPM 1.1 RPM软件包命名格式 1.2RPM功能 1.3查询已安装的软件&#xff1a;rpm -q 查询已安装软件的信息 1.4 挂载&#xff1a;使用硬件&#xff08;光驱 硬盘 u盘等&#xff09;的方法&#xff08;重点&#xff01;&#xff01;&#xff01;&#xff09; 1…

n8n - AI自动化工作流

文章目录 一、关于 n8n关键能力n8n 是什么意思 二、快速上手 一、关于 n8n n8n是一个具有原生AI功能的工作流自动化平台&#xff0c;它为技术团队提供了代码的灵活性和无代码的速度。凭借400多种集成、原生人工智能功能和公平代码许可证&#xff0c;n8n可让您构建强大的自动化…

GWAS数据和软件下载

这部分主要是数据获取,以及软件配置方法。 一、配套数据和代码 数据和代码目前在不断的更新,最新的教程可以私信,我通过后手动发送最新版的pdf和数据代码。发送的压缩包,有电子版的pdf和数据下载链接,里面是最新的百度网盘的地址,下载到本地即可。然后根据pdf教程,结合配套的…

maven多模块项目编译一直报Failure to find com.xxx.xxx:xxx-xxx-xxx:pom:1.0-SNAPSHOT in问题

工作中项目上因为多版本迭代&#xff0c;需要对不同迭代版本升级版本号&#xff0c;且因为项目工程本身是多模块结构&#xff0c;且依然多个其他模块工程。 在将工程中子模块的pom.xml中版本号使用变量引用父模块中定义的版本号时&#xff0c;一直报Failure to find com.xxx.x…

STM32 I2C硬件配置库函数

单片机学习&#xff01; 目录 前言 一、I2C_DeInit函数 二、I2C_Init函数 三、I2C_StructInit函数 四、I2C_Cmd函数 五、I2C_GenerateSTART函数 六、I2C_GenerateSTOP函数 七、I2C_AcknowledgeConfig函数 八、I2C_SendData函数 九、I2C_ReceiveData函数 十、I2C_Sen…

JavaEE初阶——计算机工作原理

一、什么是JavaEE JavaEE&#xff08;Java Platform&#xff0c;Enterprise Edition&#xff09;是sun公司&#xff08;2009年4月20日甲骨文将其收购&#xff09;推出的企业级应用程序版本。这个版本以前称为 J2EE。能够帮助我们开发和部署可移植、健壮、可伸缩且安全的服务器…

【微服务】2、网关

Spring Cloud微服务网关技术介绍 单体项目拆分微服务后的问题 服务地址问题&#xff1a;单体项目端口固定&#xff08;如黑马商城为8080&#xff09;&#xff0c;拆分微服务后端口各异&#xff08;如购物车808、商品8081、支付8086等&#xff09;且可能变化&#xff0c;前端难…