数据结构——红黑树

2023-09-18 18:37:42

1.什么是红黑树?

红黑树是一种特定类型的二叉树,用于组织数据。它是一种平衡二叉查找树(AVL树)的变体,每个结点都带有颜色属性(红色或黑色)。在红黑树中,从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。具体来说,红黑树满足以下性质:

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

举个例子

2.红黑树插入的模拟实现

①节点的定义

// 在这里为了方便表示我们先将颜色枚举
// 在红黑树中只有黑与红,即BLACK与RED
enum Color
{
	BLACK,
	RED
};

// 因为节点是公用的,因此设定为struct
template<class K, class V>
struct RBTreeNode
{
	RBTreeNode(const pair<K, V>& kv)
		:_kv(kv)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		// 根据红黑树的性质可以知道
		// 要插入的新节点不应该是黑色的,而应该是红色的
		// 如果是黑色的那么就会影响它当前路径的黑色节点总数
		, _color(RED) 
	{}

	pair<K, V> _kv;
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	Color _color;
};

②插入

template<class K, class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;

	bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}

		Node* cur = _root;
		Node* parent = nullptr;
		// 寻找要插入的位置
		while (cur)
		{
			if (kv.first < cur->_kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (kv.first > cur->_kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}

		// 到此处cur已经指向了应该插入的位置,
		// 然后判断应该插入到parent的哪边
		cur = new Node(kv);
		if (kv.first > parent->_kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;

		// 插入完成后需要对红黑树进行调整
		// 若父节点是黑就无需调整
		// 而当父节点是红就需要进行调整
        // ...
	}

private:
	Node* _root = nullptr;
};

以上面的红黑树为例,在parent为红的情况下,插入之后的情况可以将它们大致分为两种:1. uncle存在且为红;2.uncle不存在或uncle存在且为黑。让我们来分析一下,在这里我们依旧采取先具体后推广到一般的方法

1.uncle存在且为红

先举一个具体例子

 抽象图如下

2.uncle不存在或uncle存在且为黑

先举一个uncle不存在的具体例子

再举一个uncle存在且为黑的具体例子 

观察上述例子之后可以发现,这两种情况本质上可以当做同一操作,那就是先判断grandpa、parent与cur的关系,然后根据具体情况进行旋转,旋转之后进行变色,因此抽象图如下

 

综合上面的所有情况,再加上一些细节我们可以得到如下插入代码

bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}

		Node* cur = _root;
		Node* parent = nullptr;
		// 寻找要插入的位置
		while (cur)
		{
			if (kv.first < cur->_kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (kv.first > cur->_kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}

		// 到此处cur已经指向了应该插入的位置,
		// 然后判断应该插入到parent的哪边
		cur = new Node(kv);
		if (kv.first > parent->_kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;

		// 插入完成后判断一下
		// 若父节点是黑就无需调整
		// 而当父节点是红就需要进行调整
		while (parent && parent->_color == RED)
		{
			Node* grandpa = parent->_parent;
			if (parent == grandpa->_left)
			{
				Node* uncle = parent->_right;
				if (uncle && uncle->_color == RED)
				{
					uncle->_color = parent->_color = BLACK;
					grandpa->_color = RED;

					cur = grandpa;
					parent = cur->_parent;
				}
				else
				{
					if (uncle == nullptr)
					{
						if (parent->_left == cur)
						{
							//              grandpa
							//      parent
							//cur
							RotateR(grandpa);
							grandpa->_color = RED;
							parent->_color = BLACK;
						}
						else
						{
							//              grandpa
							//      parent
							//              cur
							RotateL(parent);
							RotateR(grandpa);
							cur->_color = BLACK;
							grandpa->_color = RED;
						}
					}
					else // uncle存在且为黑
					{
						if (parent->_left == cur)
						{
							//              grandpa
							//      parent
							//cur
							RotateR(grandpa);
							grandpa->_color = RED;
							parent->_color = BLACK;
						}
						else
						{
							//              grandpa
							//      parent
							//              cur
							RotateL(parent);
							RotateR(grandpa);
							cur->_color = BLACK;
							grandpa->_color = RED;
						}
					}

					break;
				}
			}
			else // parent == grandpa->_right
			{
				Node* uncle = parent->_left;
				if (uncle && uncle->_color == RED)
				{
					uncle->_color = parent->_color = BLACK;
					grandpa->_color = RED;

					cur = grandpa;
					parent = cur->_parent;
				}
				else
				{
					if (uncle == nullptr)
					{
						if (parent->_left == cur)
						{
							//grandpa
							//         parent
							//cur
							RotateR(parent);
							RotateL(grandpa);
							cur->_color = BLACK;
							grandpa->_color = RED;
						}
						else
						{
							//grandpa
							//        parent
							//               cur
							RotateL(grandpa);
							grandpa->_color = RED;
							parent->_color = BLACK;
						}
					}
					else // uncle存在且为黑
					{
						if (parent->_left == cur)
						{
							//grandpa
							//         parent
							//cur
							RotateR(parent);
							RotateL(grandpa);
							cur->_color = BLACK;
							grandpa->_color = RED;
						}
						else
						{
							//grandpa
							//        parent
							//               cur
							RotateL(grandpa);
							grandpa->_color = RED;
							parent->_color = BLACK;
						}
					}

					break;
				}
			}
		}

		return true;
	}

(注:左右旋参见数据结构——AVL树) 

3.红黑树的性能分析

红黑树可以在O(log n)时间内做查找、插入和删除,这里的n 是树中元素的数目。红黑树在最坏情况下的运行时间也是非常良好的,并且在实践中是高效的。红黑树的性能受限于它的平衡性,即每个叶子节点到根节点的路径上所包含的黑色节点的数量是相同的,这也被称为黑色完美平衡。保持这种平衡可以确保红黑树的性能稳定。总的来说,红黑树是一种高效的数据结构,适用于许多不同的情况。

4.红黑树与AVL树的比较

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

更多推荐

Scotch: Combining SGX and SMM to Monitor Cloud Resource Usage【TEE的应用】

目录摘要引言贡献背景SMMXenCreditScheduler与资源核算SGX威胁模型SchedulerattacksResourceinterferenceattacksVMEscapeattacks架构ResourceAccountingWorkflowCostofAccounting具体的部署和评估见论文相关工作

自监督学习之对比学习:MoCo模型超级详解解读+总结

文章目录一、MoCo简介1.1整体思想1.2动量1.3正负样本如何选取二、动态字典2.1query和key2.2字典特点三、编码器的动量更新3.1编码器的更新规则3.2使用动量更新的原因四、实验过程4.1目标函数:infoNCE4.1.1softmax4.1.2交叉熵损失4.1.3交叉熵损失函数和softmax的关系4

比特币 ZK 赏金系列:第 2 部分——查找哈希冲突

在我们的零知识赏金(ZKB)系列的第二部分中,我们将其应用于解决哈希冲突难题。在这样的谜题中,两个不同的输入散列到相同的输出。此类赏金可用于:充当煤矿中的金丝雀,给我们一个有价值的提醒。存在冲突是散列函数较弱的标志,因此我们可以尽早升级以减轻损失。资助研究以发现哈希函数中的漏洞,特别是对于MiMC等新函数。碰撞攻击历史

前端工程化面试题

下面的模块导出了什么结果?exports.a='a';module.exports.b='b';this.c='c';module.exports={d:'d'}参考答案:{d:'d'}说一下你对前端工程化,模块化,组件化的理解?参考答案:这三者中,模块化是基础,没有模块化,就没有组件化和工程化模块化的出现,解决了困扰

4 vCPU 实例达成 100 万 JSON API 请求/秒的优化实践

“性能工程”(Performanceengineering)是个日渐流行的概念。顾名思义“性能工程”是包含在系统开发生命周期中所应用的一个技术分支,其目的就是确保满足非功能性的性能需求,例如:性能、可靠性等。由于现代软件系统变得日益复杂,我们在对抗性能这个凸显的挑战的时候往往显得无措手足,或者照本宣科的尝试一些偏方,寄

人脸修复祛马赛克算法CodeFormer——C++与Python模型部署

一、人脸修复算法1.算法简介CodeFormer是一种基于AI技术深度学习的人脸复原模型,由南洋理工大学和商汤科技联合研究中心联合开发,它能够接收模糊或马赛克图像作为输入,并生成更清晰的原始图像。算法源码地址:https://github.com/sczhou/CodeFormer这种技术在图像修复、图像增强和隐私保护

OceanBase开源获信通院认可:开源300万行核心代码、社区答疑超3万次

昨日,在由中国信息通信研究院主办的“2023OSCAR开源产业大会”上,蚂蚁集团旗下的自研原生分布式数据库OceanBase荣获“2023OSCAR尖峰开源项目”、“2023OSCAR尖峰开源企业(开源运营与生态建设)”两个奖项。同时,完成可信开源社区评估,获得“可信开源社区”评估证书。OceanBase自2021年6

从0搭建夜莺v6基础监控告警系统(二):采集数据、打通夜莺显示

文章目录1.写在前面1.1.categraf采集数据1.2.官方文档传送门2.配置过程2.1.打通夜莺和VictoriaMetrics2.2.配置Categraf2.3.验证结果2.4.配置仪表盘3.部署总结3.1.操作总结3.2.仪表盘展示上一操作我们已经安装好了所需的基础服务,接下来需要打通各组件之间的数据推送和监

Kubernetes Dashboard安装部署

KubernetesDashboard安装部署1.下载Dashboard部署文件2.修改yaml配置文件3.应用安装,查看pod和svc4.创建dashboard服务账户5.创建admin-user用户的登录密钥6.登录6.1使用token登录(1)短期token(2)token长期有效6.2使用Kubeconfig文

Pyppeteer中文文档

介绍Pyppeteer是PuppeteerJavascript(无头)chrome/chromium浏览器自动化库的Python非官方端口,Puppeteer是在Node.js中使用的,而Pyppeteer是专用于Python语言的。本文档对应的是Pyppeteer的v0.0.25版本,从目前情况来看,Pyppetee

手机无人直播手机用哪些软件系统最好?

最近手机无人直播可是风靡大江南北,只要是一个抖音用户都想装个手机无人直播软件,随时随地开启手机无人直播,抖音8亿用户想想这个市场得有多大,蛋糕有多肥。那么问题来了,手机无人直播手机用啥软件?推荐:魔棒直播这里有多个理由我们还必须要选择它;1.因为手机无人直播软件,目前市面上清一色仅支持安卓版,支持苹果版的只有“魔棒直播

热文推荐