[数据结构] RBTree 模拟实现RBTree

news2024/9/24 23:31:19

标题:[数据结构] RBTree && 模拟实现RBTree

@水墨不写bug



目录

一、红黑树的概念

二、map和set的封装

 三、红黑树的实现

1、红黑树节点的定义

2、红黑树的结构

3、红黑树的插入

1.名称 

 2.插入节点的颜色

红黑树的insert 实现

情况一:不能确定的uncle  为红  

情况二: uncle  不存在/uncle 存在且为黑  


 

 正文开始:

一、红黑树的概念

        普通二叉树结构在存储数据时,相对于链式结构并没有太大优势,于是就出现了搜索二叉树,搜索二叉树的优势在于在查找时,一般情况下复杂度可以控制在O(logN)。但是普通的二叉搜索树无法避免的会出现一种特殊情况当插入近似有序的值时,搜索树会退化为单链表。

        解决二叉树退化为链表的问题,也就是让二叉树尽量平衡,有多重方法:如AVL树,红黑树等。本文就介绍红黑树——一种令二叉搜索树尽量平衡的策略。


        红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。

(红黑树概貌)

 红黑树的规则:

        1、每个节点只有两种颜色(红、黑)。

        2、根节点为固定的颜色:黑色。

        3、如果一个节点是红色的,则它的两个孩子节点是黑色的;(或者说:没有连续的红节点)

        4、对于树上的每一个节点,从该节点到其所有后代叶节点的简单路径上,都包含相同数目的黑色节点。

        5、每个叶节点是黑色的。(此处的叶节点指空节点)

        思考:为什么满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点个数的两倍?

        由规则4,从根节点出发到达任意一个叶节点的路径上,黑节点的数目是相同的。拉开差别的是红节点。由规则3,红节点只能存在于两个黑节点之间,所以最短路径就是纯黑节点,最长路径就是每两个黑节点之间存在一个红节点,最终叶子节点也是红。当且仅当这时,最短路径长度*2刚好等于最长路径的长度。 于是最长路径中节点个数不会超过最短路径节点个数的两倍。 


二、map和set的封装

        map和set在底层实现的时候,是通过封装红黑树来实现的。set看上去只有一个key,map看上去有一对值<key,val>;但是实际上在实现时,key和数据是分开的:

        set在复用红黑树时,传入的模板参数包括Key和Value,只不过两者相等,都是整形。

        map在复用红黑树时,为了保持一致,传入的模板参数包括Key和Value,只不过Value是自定义的结构体类型,也就是pair<K,V>。

        (也许你在思考后仍然不理解,不过好在我会在后面专门写一篇map/set封装的文章,具体讲解封装的细节)


 三、红黑树的实现

1、红黑树节点的定义

        在此,由于我们暂时不考虑map、set的封装,所以我们的红黑树内部的数据固定写为pair类:(传入的两个模板参数为Key,Value)

enum COLOR
{
	BLACK,
	RED
};

template<class K,class V>
struct BRTreeNode
{
	BRTreeNode<K, V>* _parent;
	BRTreeNode<K, V>* _left;
	BRTreeNode<K, V>* _right;
	pair<K, V> _kv;
	COLOR _col;

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

 

2、红黑树的结构

         由于实现红黑树的难点在于插入后的节点颜色控制与极度不平衡时的旋转,所以我们主要实现红黑树的插入逻辑。

template<class K,class V>
class BRTree
{
	typedef BRTreeNode<K, V> Node;
public:
	BRTree();
		
	bool insert(const pair<K,V>& kv);
	
    ~BRTree();
	
private:
	//左单旋
	void RotateL(Node* parent);
	
	//右单旋
	void RotateR(Node* parent);
	
	Node* _root;
};

 

3、红黑树的插入

        红黑树的插入分有多种情况,在分情况讨论之前,我们现明确几个概念并达成几个共识:

1.名称 

当插入节点的时候:

        新增节点称为cur;

        父节点称为parent;

        祖父节点称为grandparent;

        祖父节点除父亲节点外另一节点称为uncle;

他们在本文中都用首字母作为简写。

 2.插入节点的颜色

        当我们插入节点的时候,插入节点的颜色是什么呢?

        当插入的是黑节点,那么从根到每一个叶节点的黑节点数目不同,整棵树都不再是红黑树。

        当插入的是红节点,会有两种情况:如果父亲为黑,那么插入后这棵树仍是合法的红黑树。如果父亲为红,违背了不能存在连续的红节点,整棵树不再是红黑树。

        于是,可以得出结论:插入节点的颜色应该为红色,这样对整棵树的影响可以达到最小。

        当我们插入的是红节点时,我们如果父亲是黑,则不需调整;如果父亲是红,这就到了需要调整的时候:

插入为红,父亲为红,可以推知祖父为黑。

         使用假设法,如果祖父为红,则父亲和祖父两个节点在插入新增节点之前就是连续的红节点了,已经违背了红黑树的规则。


        所以到这里,我们可以确定插入节点(cur)为红,父亲(parent)为红,祖父(grandparent)为黑,而唯一不能确定的就是uncle节点的颜色和状态,于是uncle节点的状态和颜色就是分类讨论的依据。

红黑树的insert 实现

情况一:不能确定的uncle  为红  

        这种情况是比较简单的情况,只需要简单的变色就可以解决问题:

变色策略:p和u变为黑,g变为红。

        接下来需要将g看做是cur,继续向上变色调整。 


其实上述情况会有一种变式,具体结构如下:

        红色四边形表示只含有红色节点(其实就是一个红节点),黑色的四边形表示只含有一个黑色节点的红黑树。 

        如果新增节点在红色四边形上,那么就会导致22这个黑色节点变成红色,具体来说如下:

        新插入的节点是黑字的cur

        变色后:导致22节点颜色变为红色

        这个时候,黑色的g当成cur继续向上调整:

        这就间接转化为第一种情况的最简结构了:

        所以,要达到第一种情况,可能是插入后直接达到;也可能是向上调整后达到。第一种情况的最简形态的逻辑已经包含了所有其他形态的逻辑。

 

情况二: uncle  不存在/uncle 存在且为黑  

        uncle不存在:(四种情况)

 情况A:右单旋

旋转后变色:

 


情况B:左右双旋

旋转后变色:

 


情况C:左单旋

旋转后变色:


 情况D:右左双旋

 旋转后变色:

 

       


        旋转已经在AVL树的模拟实现中有详细的讲解,如果你对AVL树不熟悉,参考这篇:

《AVL树详解与模拟实现》

         这四种情况是uncle不存在的情况,也是uncle存在且为黑的情况的最简形态。是的,uncle存在且为黑的逻辑与uncle不存在的逻辑是一致的。


         uncle存在且为黑:

         具体分析我们可以得知:如果d、e为空,那么a、b、c中有一个黑节点。(全部情况以此类推,只要保证每条路径的黑节点数目相同即可)

         在调整过程中,会出现如下四种情况:(可以确定,这些情况就是上述uncle不存在时的四种情况的变式)

 

 

 

 这里不在重复旋转的过程。

        综上,我们要实现红黑树插入的逻辑,就需要分两种大情况来讨论,这两种情况分别是:

        uncle存在且为红;

        uncle不存在,或者存在且为黑;

        这两种情况都有一个最简的形态,最简形态的逻辑与其他形态的逻辑基本一致,所以红黑树的逻辑并不难理解。

        虽然红黑树的逻辑不难理解,但是难点在于实现时的判断,通过树的结构判断需要走哪种逻辑。

        这里,给出红黑树的实现,作为参考;

	enum COLOR
	{
		BLACK,
		RED
	};

	template<class K,class V>
	struct BRTreeNode
	{
		BRTreeNode<K, V>* _parent;
		BRTreeNode<K, V>* _left;
		BRTreeNode<K, V>* _right;
		pair<K, V> _kv;
		COLOR _col;

		BRTreeNode(const pair<K,V>& kv = pair<K,V>())
			:_parent(nullptr)
			,_left(nullptr)
			,_right(nullptr)
			,_kv(kv)
			,_col(RED)
		{}
	};
	template<class K,class V>
	class BRTree
	{
		typedef BRTreeNode<K, V> Node;
	public:
		BRTree()
			:_root(nullptr)
		{}
		bool insert(const pair<K,V>& kv)
		{
			//对于空的特殊处理
			if (_root == nullptr)
			{
				_root = new Node(kv);
				_root->_col = BLACK;//根节点为黑
				return true;
			}
			//找到插入位置
			Node* cur = _root, * parent = nullptr;
			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);
				if (cur->_kv.first < kv.first)
				{
					parent->_right = cur;
				}
				else
				{
					parent->_left = cur;
				}
				cur->_parent = parent;
				//以上的逻辑与二叉搜索树完全一致,到这里二叉树逻辑结束,红黑树开始
				//根据树的结构 分情况讨论
				//cur为红,p为红,g为黑,
				while (parent &&cur->_parent->_col == RED)
				{
					Node* grandfather = parent->_parent;
					if (parent == grandfather->_left)
					{
						//uncle在右侧
						//        g
						//   p          u
						//	
						Node* uncle = grandfather->_right;
						if (uncle && uncle->_col == RED)//u存在且为红
						{
							parent->_col = uncle->_col = BLACK;
							grandfather->_col = RED;

							//继续向上处理
							cur = grandfather;
							parent = cur->_parent;
						}
						else//u不存在或者u存在且为黑
							//旋转
						{
							if (cur == parent->_left)
								//单旋
							{
								RotateR(grandfather);
								grandfather->_col = RED;
								parent->_col = BLACK;
							}
							else
								//双旋
							{
								RotateL(parent);
								RotateR(grandfather);
								grandfather->_col = RED;
								parent->_col = BLACK;
							}
						}
					}
					else
					{
						//uncle在左侧
						//        g
						//   u          p
						//					
						Node* uncle = grandfather->_left;
						if (uncle && uncle->_col == RED)//uncle存在且为红
						{
							parent->_col = uncle->_col = BLACK;
							grandfather->_col = RED;

							cur = grandfather;
							parent = grandfather->_parent;
						}
						else//uncle不存在或者存在且为黑
							//旋转
						{
							if (cur == parent->_right)
								//单旋
							{
								RotateL(grandfather);
								grandfather->_col = RED;
								parent->_col = BLACK;
							}
							else
								//双旋
							{
								RotateR(parent);
								RotateL(grandfather);
								grandfather->_col = RED;
								parent->_col = BLACK;
							}
						}
					}
				}
				//无论如何变化颜色,根节点的颜色总是黑色
				_root->_col = BLACK;
				return true;
			}
		}
	private:
		//左单旋
		void RotateL(Node* parent)
		{
			Node* subL = parent->_left, * subLR = subL->_right;
			Node* Parentparent = parent->_parent;
			
			//调整子树内部
			subL->_right = parent;
			parent->_parent = subL;

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

			//调整子树与Parentparent
			if (Parentparent == nullptr)
			{
				_root = subL;
				subL->_parent = nullptr;
			}
			else
			{
				if (parent == Parentparent->_left)
				{
					subL = Parentparent->_left;
				}
				else
				{
					subL = Parentparent->_right;
				}
				subL->_parent = Parentparent;
			}
		}
		//右单旋
		void RotateR(Node* parent)
		{
			Node* subR = parent->_right, * subRL = subR->_left;
			Node* Parentparent = parent->_parent;

			//子树内部关系
			subR->_left = parent;
			parent->_parent = subR;

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

			//子树与Parentparent关系
			if (Parentparent == nullptr)
			{
				_root = subR;
				subR->_parent = nullptr;
			}
			else
			{
				if (parent == Parentparent->_left)
				{
					subR = Parentparent->_left;
				}
				else
				{
					subR = Parentparent->_right;
				}
			}
		}
		Node* _root;
	};

         到这里,除了删除操作以及一些其他的简单接口,红黑树的实现基本完成了。

        红黑树是STL一些重要容器的底层结构,理解红黑树对于深入了解STL有重要意义。


完~

未经作者同意禁止转载

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

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

相关文章

微信自动化管理了解下

微信作为一款广泛使用的社交软件&#xff0c;已经成为人们日常生活中不可或缺的通讯工具。不仅个人用户频繁使用&#xff0c;许多企业也依赖微信进行业务沟通和客户服务。 然而&#xff0c;对于企业用户来说&#xff0c;管理多个微信账户往往带来诸多繁琐和不便之处。这些问题…

Django+anaconda

一、搭建django虚拟环境 打开anaconda prompt 输入&#xff1a;conda create -n mydjango_env 判断&#xff08;y/n&#xff09;:y 查看虚拟环境 conda env list *号表示当前使用的环境 激活创建的虚拟环境 activate mydjango_env 二、安装Django 在新环境激活的状态下安装…

Nature|通过范德华层压实现三维单片集成系统 (半导体器件/集成电路)

2024年5月22日,湖南大学刘渊(Yuan Liu)教授课题组,在《Nature》上发布了一篇题为“Monolithic three-dimensional tier-by-tier integration via van der Waals lamination”的论文。第一作者为湖南大学物理与微电子科学学院陆冬林(Donglin Lu)博士。论文内容如下: 一、 …

Stable Diffusion 的采样器

一图 不推荐使用的采样器 PLMS LMS LMS Karras DPM fast DPM2 DPM2a DPM2 Karras DPM2 a Karras 可以在设置里把采样器去掉

同态加密和SEAL库的介绍(十)CKKS 参数心得 2

写在前面&#xff1a; 本篇继续上篇的测试&#xff0c;首先针对密文深度乘法情况&#xff0c;虽然密文乘法本就是应该尽量避免的&#xff08;时间和内存成本过高&#xff09;&#xff0c;更不用说深度乘法了&#xff0c;但是为了测试的完整性&#xff0c;还是做一下方便大家比对…

CVE-2021-21315漏洞复现

一、基本信息 攻击机&#xff1a;kali IP:192.168.100.60 靶机&#xff1a;CentOS7 IP:192.168.100.40 二、攻击过程 下载node.js环境 wget https://nodejs.org/dist/v12.18.4/node-v12.18.4-linux-x64.tar.xz tar -xvf node-v12.18.4-linux-x64.tar.xz mv node-v12.18.4-…

89.WEB渗透测试-信息收集-Google语法(3)

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a; 易锦网校会员专享课 上一个内容&#xff1a;88.WEB渗透测试-信息收集-Google语法&#xff08;2&#xff09; 常用的 Google 语法的作用…

【大数据平台】可扩展性设计

欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;欢迎订阅相关专栏&#xff1a; 工&#x1f497;重&#x1f497;hao&#x1f497;&#xff1a;野老杂谈 ⭐️ 全网最全IT互联网公司面试宝典&#xff1a;收集整理全网各大IT互联网公司技术、项目、HR面试真题.…

线程间同步的概念

一、线程间同步的概念 rtthread通过线程间同步建立线程间的执行顺序&#xff0c;多个线程访问的同一个内存叫做临界区。rtthread提供的同步的工具 1、信号量 2、互斥量 3、事件集 二、信号量 2.1 信号量概念 rtthread将信号量抽象成rt_semaphore. 2.2 信号量api 2.3 信号量示例…

本地Docker部署开源Web相册图库Piwigo与在线远程访问实战方案

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

【leetcode详解】T3137(思路详解 代码优化感悟)

思路详解 要解决这个问题&#xff0c;我们的大致思路是这样&#xff1a;找到长度为k的字符串 (记为stringA) &#xff0c;统计重复次数最多的那一个&#xff0c;则最终对应的k周期字符串就是 [stringA * n] 的形式( n word.length() / k&#xff09; 要实现多对象的计数&…

【数据结构】关于Java对象比较,以及优先级队列的大小堆创建你了解多少???

前言&#xff1a; &#x1f31f;&#x1f31f;Hello家人们&#xff0c;这期讲解对象的比较&#xff0c;以及优先级队列堆&#xff0c;希望你能帮到屏幕前的你。 &#x1f308;上期博客在这里&#xff1a;http://t.csdnimg.cn/MSex7 &#x1f308;感兴趣的小伙伴看一看小编主页&…

分享一个基于SpringBoot的物品代购系统的设计与实现(源码、调试、LW、开题、PPT)

&#x1f495;&#x1f495;作者&#xff1a;计算机源码社 &#x1f495;&#x1f495;个人简介&#xff1a;本人 八年开发经验&#xff0c;擅长Java、Python、PHP、.NET、Node.js、Android、微信小程序、爬虫、大数据、机器学习等&#xff0c;大家有这一块的问题可以一起交流&…

从零开始学嵌入式技术之数字电路

一&#xff1a;数字电路基础 数字电路是现代科技和工程领域中不可或缺的基础。从计算机系统到通信设备&#xff0c;从家庭电子产品到工业自动化&#xff0c;数字电路无处不在&#xff0c;影响着我们的生活和工作。本章节旨在向读者介绍数字电路的基本概念、原理和应用&#xff…

迭代器失效

一、什么是迭代器失效 迭代器的主要作用就是让算法能够不用关心底层数据结构&#xff0c;其底层实际就是一个指针&#xff0c;或者是对指针进行了封装&#xff0c;比如&#xff1a;vector的迭代器就是原生态指针T* 。因此迭代器失效&#xff0c;实际就是迭代器底层对应指针所指…

Kubernetes之Probe探针

目录 存活、就绪和启动探针 存活探针&#xff08;Liveness Probe&#xff09; 就绪探针&#xff08;Readiness Probe&#xff09; 启动探针&#xff08;Startup Probe&#xff09; 检测方式&#xff1a; exec&#xff1a; HTTP GET&#xff1a; TCP Socket&#xff1a; …

linux DHCP和VSFTP原理与配置

目录 一、DHCP工作原理 1.1 了解DHCP服务 1.1.1 DHCP基本描述 1.1.2 使用DHCP的好处 1.1.3 DHCP的分配方式 1.2 DHCP的租约过程 1.3 使用DHCP动态配置主机地址 1.4 安装DHCP服务器 二、DHCP服务器的配置 2.1 实验环境准备 2.2 实验实战示列 三、DHCP客户端的使用 …

【数据结构】汇总八、排序算法

排序Sort 【注意】本章是 排序 的知识点汇总&#xff0c;全文1万多字&#xff0c;含有大量代码和图片&#xff0c;建议点赞收藏&#xff08;doge.png&#xff09;&#xff01;&#xff01; 【注意】在这一章&#xff0c;记录就是数据的意思。 排序可视化网站&#xff1a; D…

Python - PyQt5环境搭建与基本配置和使用教程

****前期准备&#xff1a;PyQt5以及其他组件的下载与安装 python的图形界面开发过程中&#xff0c;我们需要三个组件&#xff0c;分别是&#xff1a;PyQt5、pyqt5-tools、PyQt5Designer 一、安装 确保Python和pip已安装&#xff1a; PyQt5是基于Python的图形用户界面库&…

WEB渗透免杀篇-Bypass-AMSI

往期文章 WEB渗透免杀篇-加载器免杀-CSDN博客 WEB渗透免杀篇-分块免杀-CSDN博客 WEB渗透免杀篇-Powershell免杀-CSDN博客 WEB渗透免杀篇-Python源码免杀-CSDN博客 WEB渗透免杀篇-C#源码免杀-CSDN博客 WEB渗透免杀篇-MSFshellcode免杀-CSDN博客 WEB渗透免杀篇-Bypass-AMSI-…