C++的红黑树

news2024/11/18 10:29:29

目录

基本概念

插入结点的颜色

判断性质是否破坏

调整方式

 u为g的右孩子

u存在且为红

u存在且为黑

u不存在

结论 

红黑树结点定义

代码实现


基本概念

1、红黑树是一种特殊的二叉搜索树,每个结点会增加一个存储位表示结点的颜色(红或黑)

2、红黑树通过对所有从根到叶子的路径上的各个结点的着色方式的限制,确保红黑树中没有一条路径会比其它路径长出两倍,从而使二叉搜索树接近平衡

3、AVL树是一个严格平衡(平衡因子,二叉树能做到的最好的平衡),而红黑树是一个近似平衡插入100万个数据,AVL树会进行大量的旋转,形成的树可能是30层,但是红黑树的旋转会少一些,形成的树可能是60层,但是对于CPU而言其实二者差别也不大

4、红黑树有五个性质(紫色是重点)

  1. 每个结点不是红色就是黑色
  2. 根结点是黑色的
  3. 如果一个结点是红色,则它的两个孩子结点都是黑色(一个黑结点的孩子可以是红也可以是黑的,不可以不存在连续的红红、可以存在连续黑红、红黑和黑黑的结点)
  4. 对于每个结点,该结点到其所有后代叶子结点的简单路径上,均包含相同数目的黑色结点(一个结点向下的每条路径中的黑色结点相同,空结点也是黑色结点)(最难维护的规则)
  5. 每个叶子结点都是黑色的(红黑树中所指的叶子结点是空结点)

5、满足以上五条性质就能保证红黑树的最长路径 <= 最短路径 * 2

  • 最短路径(极端情况):全黑(由性质4可得:其它路径中也有相同数目的黑色结点以及红色结点,但是全黑的路径中没有红色结点)
  • 最长路径(极端情况):一黑一红(不算空结点)
  • 一个红黑树肯定有最长或最短路径,只不过不一定有以上的理想情况下的最短和最长路径,正常情况下最长路径小于最短路径的2倍,极端情况下最长路径可能还会等于最短路径的2倍(没有极端情况就矮子里挑将军了)

插入结点的颜色

问题:插入结点时还可以依据二叉搜索树的插入规则吗?插入结点应该都是什么颜色?

解释:可以且新插入的结点应该都是红色,因为插入结点的颜色是红色那么你只是可能违反性质3如果一个结点是红色,则它的两个孩子结点都是黑色):

        但是如果插入结点的颜色是黑色则一定违法性质4对于每个结点,该结点到其所有后代叶子结点的简单路径上,均包含相同数目的黑色结点):

判断性质是否破坏

        规定cur表示新插入结点,p表示父结点(parent)、g表示爷爷结点(grandfather)、u表示叔叔结点(uncle)

问题:如何确认在插入新结点后,红黑树的性质是否被破坏?

解释:新结点的颜色为红色,如果新结点的父结点颜色为红色,则违法性质3,需要进行调整

补充:p一定为红,g一定为黑,因为新插入结点的颜色是红色,插入该结点导致需要调整只可能是因为该结点的插入导致性质3遭到了破坏,那么p结点一定为红,而g也肯定为黑,因为在新插入结点前整颗红黑树应该是没有错误的(跟AVL树旋转是一个道理)

调整方式

问题:发现红黑树性质被破坏后,如何进行调整?(后面还有更准确的划分)

由上述内容可知,在性质被破坏时cur一定为红、p一定为红、g一定为黑,那么关键点就在于p的兄弟结点u,对u的关注点应该在于它的“位置 + 存在情况”

②u的位置:p为g的左孩子,u就是g的右孩子,p为g的右孩子,u就是g的左孩子

③u的存在情况:u存在且为黑色,u存在且为红色、u不存在

 u为g的右孩子

u存在且为红

解决办法:将p和u变为黑,g变为红(p和u替代了g保证路径上黑色结点数量不变)

遗留问题:

  • 如果g是根结点,那么在g变红后为了满足性质2,仅需要将g重新变为黑色即可
  • 如果g不是根结点,那么在g变红且g的父结点的颜色是黑色时,不需要继续向上调整
  • 如果g不是根结点,那么在g变红且g的父结点的颜色是红色时,就需要继续向上调整

注意事项:

1、不能在发现新插入结点导致违法性质3后将cur再变为黑,因为那样还不如规定插入结点的颜色为黑色但是那样又一定会违法性质4

2、p和u改变后不能让g仍然为黑,因为如果g不是根结点,那么也会违法性质4

关于cur:无论cur是p的左孩子还是右孩子,当u存在且为红时,调整时只需要调色即可

u存在且为黑

补充:下图演示的是u存在且为红在调整后,向上调节时遇到的u存在且为黑的情况,用一个例子将u存在且为红 + 向上调整 + u存在且为黑的情况都总结了

解决办法:先旋转(以g为旋转点进行LL右单旋),后变色(p变为黑,g变为红)

关于cur:

①上面的cur是p左孩子则以g为旋转点进行LL右单旋,p变黑g变红

②若cur是p的右孩子先以p为旋转点进行左单旋,后以g为旋转点进行右单旋,cur变黑g变红

(颜色改变没有先后顺序,只要发生改变即可,先旋转后变色只是为了理解变色原因)

u不存在

解决办法:先变色(p变为黑,g变为红),后旋转(以g为旋转点进行LL右单旋)

关于cur:

①上面的cur是p左孩子则以g为旋转点进行LL右单旋,p变黑g变红

②若cur是p的右孩子先以p为旋转点进行左单旋,后以g为旋转点进行右单旋,cur变黑g变红

(颜色改变没有先后顺序,只要发生改变即可,先旋转后变色只是为了理解变色原因)

u为g的右孩子的情况不再分析,通过最终代码自己画图感受,主要区别在于旋转方式的不同

结论 

  1. 每一次的调整都可能导致另一次红黑树性质的被破坏
  2. cur和parent均为红色是由“新增”或“向上调整”两种原因导致的,因此无论是这两种的哪一种,只有当cur和parent均不为红时才不需要继续调整(循环)
  3. 红黑树是否调整取决于cur和parent是否均为红,怎么调整取决于叔叔u的情况 + cur是p的哪个孩子

红黑树结点定义

// 结点颜色
enum Color 
{ 
	RED, 
	BLACK
};

// 红黑树节点的定义
template<class ValueType>
struct RBTreeNode
{
	RBTreeNode(const ValueType & data = ValueType(),Color color = RED)
		: _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr)
		, _data(data), _color(color)
	{}

	RBTreeNode<ValueType>* _pLeft;		// 节点的左孩子
	RBTreeNode<ValueType>* _pRight;		// 节点的右孩子
	RBTreeNode<ValueType>* _pParent;	// 节点的双亲
	ValueType _data;				    // 节点的值域       
	Color _color;						// 节点的颜色        
};
  • 演示的内容与最终代码中变量的名字存在区别

代码实现

#pragma once

#include <iostream>
#include<vector>

using namespace std;

enum Colour
{
	RED,
	BLACK
};

template<class K, class V>
struct RBTreeNode
{
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;

	pair<K, V> _kv;
	Colour _col;

	RBTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		, _col(RED)
	{}
};



template<class K, class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;
public:
	bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			_root->_col = BLACK;//规定插入的根结点颜色为黑色
			return true;
		}

		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}

		cur = new Node(kv);
		cur->_col = RED; // 规定新增节点的颜色是红色
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;

		// 当parent指向的结点不为空,且parent指向的结点的颜色不为黑色就继续向上调整(因为cur和parent均为红可能是由新增和向上调整导致的,因此无论是这两种情况的哪一种,
		// 只有当cur和parent均不为红时才结束循环)
		while (parent && parent->_col == RED)
		{
			
			Node* grandfather = parent->_parent;//通过父亲找到爷爷,父亲的父亲信息是爷爷

			//红黑树的关键点在于判断叔叔的情况
			if (parent == grandfather->_left)//如果父亲是爷爷的左孩子,那么叔叔就只能是爷爷的右孩子
			//这里if中的叔叔一直都是爷爷的右孩子,不过还会存在为红、为黑、不存在的三种情况
			{
				Node* uncle = grandfather->_right;

				
				// 如果叔叔存在且叔叔的颜色是红色,则直接将叔叔和父亲的颜色都变为黑色,将爷爷的颜色变为红色
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					// 防止修改颜色导致上方出现新的两个连续的红色结点,需要移动指针继续向上处理
					cur = grandfather;//cur指向爷爷结点
					parent = cur->_parent;//parent指向的是cur的父结点(cur向上走,parent也要向上走)
				}
				else // 叔叔不存在,或者存在且为黑
				{
					if (cur == parent->_left)//如果cur是父亲的左孩子,进行LL右单,旋转后将父亲的颜色变为黑,将爷爷的颜色变为红
					{
						//     g  
						//   p   u
						// c 
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						//      g  
						//   p     u
						//      c 
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;
				}
			}



			else//如果父亲结点是爷爷结点的右孩子,那么叔叔结点就是爷爷结点的左孩子
			{
				Node* uncle = grandfather->_left;
				// 叔叔存在且为红,-》变色即可
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					// 继续往上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else // 叔叔不存在,或者存在且为黑
				{
					// 情况二:叔叔不存在或者存在且为黑
					// 旋转+变色
					//      g
					//   u     p
					//            c
					if (cur == parent->_right)
					{
						RotateL(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						//		g
						//   u     p
						//      c
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;
				}
			}
		}

		_root->_col = BLACK;//不管最后整棵红黑树的根结点是什么颜色(因为向上调整可能会导致根结点颜色变为红色,也有可能根结点也颜色不变)
		//我们这里都直接重新将根结点的颜色设置为黑色,这样就不需要再在循环中去判断是否到达根结点
		return true;
	}

	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

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

		subL->_right = parent;

		Node* ppNode = parent->_parent;
		parent->_parent = subL;

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

			subL->_parent = ppNode;
		}
	}

	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

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

		subR->_left = parent;
		Node* ppNode = parent->_parent;

		parent->_parent = subR;

		if (parent == _root)
		{
			_root = subR;
			_root->_parent = nullptr;
		}
		else
		{
			if (ppNode->_right == parent)
			{
				ppNode->_right = subR;
			}
			else
			{
				ppNode->_left = subR;
			}
			subR->_parent = ppNode;
		}
	}

	//对用户暴露的中序遍历红黑树接口
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

	//判断红黑树是否平衡
	bool IsBalance()
	{
		//首先根结点颜色得为黑
		if (_root->_col == RED)
		{
			return false;
		}

		int refNum = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_col == BLACK)
			{
				++refNum;
			}

			cur = cur->_left;
		}

		return Check(_root, 0, refNum);
	}

private:
	bool Check(Node* root, int blackNum, const int refNum)
	{
		if (root == nullptr)
		{
			//cout << blackNum << endl;
			if (refNum != blackNum)
			{
				cout << "存在黑色节点的数量不相等的路径" << endl;
				return false;
			}

			return true;
		}

		if (root->_col == RED && root->_parent->_col == RED)
		{
			cout << root->_kv.first << "存在连续的红色节点" << endl;
			return false;
		}

		if (root->_col == BLACK)
		{
			blackNum++;
		}

		return Check(root->_left, blackNum, refNum)
			&& Check(root->_right, blackNum, refNum);
	}

	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}

		_InOrder(root->_left);
		cout << root->_kv.first << ":" << root->_kv.second << endl;
		_InOrder(root->_right);
	}

private:
	Node* _root = nullptr;
};


//测试红黑树能否正常使用
void TestRBTree1()
{
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14,8, 3, 1, 10, 6, 4, 7, 14, 13 };
	RBTree<int, int> t1;
	for (auto e : a)
	{
		if (e == 10)
		{
			int i = 0;
		}

		// 1、先看是插入谁导致出现的问题
		// 2、打条件断点,画出插入前的树
		// 3、单步跟踪,对比图一一分析细节原因
		t1.Insert({e,e});

		std::cout << "Insert:" << e << "->" << t1.IsBalance() << std::endl;
	}

	t1.InOrder();

	std::cout << t1.IsBalance() << std::endl;
}

//测试红黑树对百万数据处理所需时间
void TestRBTree2()
{
	const int N = 1000000;
	vector<int> v;
	v.reserve(N);
	srand(time(0));

	for (size_t i = 0; i < N; i++)
	{
		v.push_back(rand() + i);
		//cout << v.back() << endl;
	}

	size_t begin2 = clock();
	RBTree<int, int> t;
	for (auto e : v)
	{
		t.Insert(make_pair(e, e));
		//cout << "Insert:" << e << "->" << t.IsBalance() << endl;
	}
	size_t end2 = clock();

	std::cout << t.IsBalance() << std::endl;
}

~over~

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

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

相关文章

如何将老板的游戏机接入阿里云自建K8S跑大模型(下)- 安装nvidia/gpu-operator支持GPU在容器中共享

文章目录 安装nvidia/gpu-operator支持GPU在容器中共享 安装nvidia/gpu-operator支持GPU在容器中共享 安装 nvidia/gpu-operator遇到两个问题&#xff1a; 由于我们都懂的某个原因&#xff0c;导致某些镜像一直现在不成功。 解决办法&#xff0c;准备一个&#x1fa9c;&#…

如何理解kmp的套娃式算法啊?

概念 KMP算法&#xff0c;全称Knuth Morris Pratt算法 。文章大部分内容出自《数据结构与算法之美》 核心思想 假设主串是a&#xff0c;模式串是b 在模式串与主串匹配的过程中&#xff0c;当遇到不可匹配的字符的时候&#xff0c;对已经对比过的字符&#xff0c;是否能找到…

FME学习之旅---day27

我们付出一些成本&#xff0c;时间的或者其他&#xff0c;最终总能收获一些什么。 教程&#xff1a;Excel 入门 查看和检查 Excel 数据 1.读模块读取EXCEL文件 2.对源数据进行预览 Excel Reader 参数 |将 Excel 转换为 CSV 阅读 2020 年和平均值工作表&#xff0c;然后计算降…

运行Android项目时,提示错误: 程序包javax.annotation.processing不存在

今天在运行项目时提示错误: 错误: 程序包javax.annotation.processing不存在 import javax.annotation.processing.Generated; 最后是修改了Android Studio的JDK的路径修改为你安装的JDK路径&#xff0c;完成的修复&#xff1a;

thinkphp 多条件查询 不起作用 = like

不起作用的代码&#xff1a; &#xff08; where([category_id > $item[id]]) 没起作用 &#xff09; 传递参数 $model->where(product_name, like, "%$productName%") 不起作用 public function cateProductPage() {$builder new Builder(new CategoryMod…

基于transformers框架实践Bert系列4-文本相似度

本系列用于Bert模型实践实际场景&#xff0c;分别包括分类器、命名实体识别、选择题、文本摘要等等。&#xff08;关于Bert的结构和详细这里就不做讲解&#xff0c;但了解Bert的基本结构是做实践的基础&#xff0c;因此看本系列之前&#xff0c;最好了解一下transformers和Bert…

利用神经网络学习语言(一)——自然语言处理的基本要素

相关说明 这篇文章的大部分内容参考自我的新书《解构大语言模型&#xff1a;从线性回归到通用人工智能》&#xff0c;欢迎有兴趣的读者多多支持。 本文涉及到的代码链接如下&#xff1a;regression2chatgpt/ch10_rnn/tokenizer.ipynb 本系列文章将深入探讨一种应用广泛的神经…

hcia datacom学习(8):静态NAT、动态NAT、NAPT、Easy IP、NAT server

1.私网地址 在现实环境中&#xff0c;企业、家庭使用的网络是私网地址&#xff08;内网&#xff09;&#xff0c;运营商维护的网络则是公网地址&#xff08;外网&#xff09;。私网地址是在局域网&#xff08;LAN&#xff09;内使用的&#xff0c;因此无法被路由&#xff0c;不…

计算机毕业设计 | springboot药品库存追踪与管理系统 药店管理(附源码)

1&#xff0c;绪论 1.1 背景调研 如今药品调价频繁&#xff0c;且品种繁多&#xff0c;增加了药品销售定价的难度。药品来货验收登记中的审查有效期环节容易出错&#xff0c;错收过期或有效期不足的药品。 手工模式下的药品库存难以及时掌握&#xff0c;虽然采取了每日进行缺…

5.23.1 深度学习在乳腺癌成像中的应用

乳腺成像在早期发现乳腺癌以及在治疗期间监测和评估乳腺癌方面发挥着重要作用。最常用的乳腺成像方式是数字乳房X线摄影、数字乳腺断层合成、超声和磁共振成像。 传统的 CAD 系统基于传统的机器学习 (ML) 技术&#xff1b;预定义&#xff08;手工制作&#xff09;的特征是系统…

元器件基础学习笔记——电感的分类及主要参数

一、电感的分类 电感器是一种电子元件&#xff0c;它能够将电能转化为磁能并储存起来。电感器的分类方法有很多&#xff0c;可以根据用途、形状、结构等不同的标准进行划分。 分类依据类型备注电感值固定电感固定线圈可变电感改变磁芯的饱和度用途高频电感绕线型&#xff0c;积…

Nest的test中的best是Jest框架

Nest的test中的best是Jest框架 前言 花了3天时间给自己之前做的一个小系统基本补完了单元测试&#xff0c;趁此机会>脑袋里对于单元测试的知识还算热乎&#xff0c;来输出一篇比较详细的关于单元测试的文章&#xff0c;以梳理知识&#xff0c;融汇贯通&#xff1b;如果对你…

配置旁挂二层组网直接转发示例(命令行)

业务需求 企业用户通过WLAN接入网络&#xff0c;以满足移动办公的最基本需求。且在覆盖区域内移动发生漫游时&#xff0c;不影响用户的业务使用。 组网需求 AC组网方式&#xff1a;旁挂二层组网。DHCP部署方式&#xff1a; AC作为DHCP服务器为AP分配IP地址。汇聚交换机SwitchB作…

vue小记——小组件(1)

代码&#xff1a; <template><div><el-steps :active"active" finish-status"success" simple><el-step title"数据导入"><i class"fa fa-cloud-upload fa-icon-custom" slot"icon"></i…

Docker搭建mysql性能测试环境

OpenEuler使用Docker搭建mysql性能测试环境 一、安装Docker二、docker安装mysql三、测试mysql连接 一、安装Docker 建立源文件vim /etc/yum.repos.d/docker-ce.repo增加内容[docker-ce-stable] nameDocker CE Stable - $basearch baseurlhttps://repo.huaweicloud.com/docker…

【手势识别-UIPinchGestureRecognizer捏合-UIPanGestureRecognizer缩放 Objective-C语言】

一、接下来,我们来说这个捏合,和,这个缩放啊 1.捏合, 首先呢,步骤,也都是一样的啊, 1)创建手势对象 2)添加手势 3)实现手势方法 pinch:捏合 UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:(id) action:(SEL)]; U…

ThreadLocal原理及使用

一、引言 在Java多线程编程中&#xff0c;ThreadLocal是一个非常有用的工具&#xff0c;它提供了一种将对象与线程关联起来的机制&#xff0c;使得每个线程都可以拥有自己独立的对象副本&#xff0c;从而避免了线程安全问题。然而&#xff0c;使用不当会导致内存泄漏问题。 二…

spring-boot整合Micrometer+Prometheus

环境&#xff1a; micrometer 1.8.2 prometheus 0.14.1 spring-boot-actuator 2.6.6 使用案例 <!-- Springboot启动actuator&#xff0c;默认会引入依赖&#xff1a;micrometer-core --> <dependency><groupId>org.springframework.boot</groupId>&l…

ctfhub中的SSRF相关例题(中)

目录 上传文件 gopher协议的工作原理&#xff1a; gopher协议的使用方法&#xff1a; 相关例题: FastCGI协议 FastCGI协议知识点 相关例题&#xff1a; Redis协议 知识点&#xff1a; 相关例题 第一种方法 第二种方法 上传文件 gopher协议的工作原理&#xff1a; …

《ESP8266通信指南》番外-(附完整代码)ESP8266获取DHT11接入(基于Lua)

前言 此篇为番外篇,是 ESP8266 入门的其他功能教程,包括但不限于 DHT11 驱动TCP 通信Thingsboard 平台的接入阿里云物联网云平台接入华为云平台接入 1. 小节目标 使用 Lua 驱动 DHT11 传感器,获取温湿度的值 2. 进入主题 NodeMCU 基于 LUA 相关资料 官方文档&#xff1a;…