数据结构【红黑树模拟实现】

news2025/1/2 0:11:03

目录

红黑树:基于AVL树改进

红黑树的性质

红黑树基本结构

insert基本结构

新增节点的默认颜色为红色

节点性质总结

情况一: cur为红,p为红,g为黑,u存在且为红

情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑(单旋+变色)

情况三: cur为红,p为红,g为黑,u不存在/u存在且为黑(双旋+变色)

insert代码实现

验证是否为红黑树

源码链接


红黑树:基于AVL树改进

AVL树控制平衡因子,严格要求左右子树高度差不超过1,所以他的效率一直都是保持在O(logN)左右,但严格要求平衡导致其需要更多次的旋转

如果不严格要求平衡,只需要达到近似平衡,保持其性能还是处于logN数量级,减少旋转次数,可提升性能(AVL树的高度平衡是因为其通过大量的旋转来完成的,所以对于经常发生删除和插入的结构,红黑树的效率会更优,并且红黑树的实现比起AVL更加容易且易于控制,所以实际中使用红黑树更多

红黑树确保最长路径不超过最短路径的二倍,在最坏情况下,增删查改效率是O(2logN)。

红黑树概念

红黑树是一种二叉搜索树,需要在每个结点增加一个存储位表示结点的颜色,分别是Red/Black。

通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保最长路径不超过最短路径的二倍,因而接近平衡

11条路径

红黑树的性质

控制住颜色的规则(满足下面的性质),红黑树就能保证:其最长路径中节点个数不会超过最短路径节点个数的两倍:

1. 每个结点不是红色就是黑色

2. 根节点是黑色的 

3. 如果一个节点是红色,则它的两个孩子结点是黑色 (没有连续红节点)

4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点(每条路径黑色节点数量相同)

5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)

(性质5出题:空树也是红黑树,性质5中规定树中的空指针域为叶子节点,因此空树也有节点)

根据规则3,4可以推导出:存在最短路径一定是全黑,存在最长路径一定是一黑一红

红黑树基本结构

基本结构与AVL树相似,只是把平衡因子换成枚举标识颜色,满足性质1

enum Color
{
	RED,
	BLACK
};

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

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

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

private:
	Node* _root;
};

insert基本结构

插入分为两个步骤:

1.按照二叉搜索树的特性插入

2.满足红黑树的性质调节颜色

insert基本结构,唯一需要改变的就是当root为根时,变为黑色,满足性质2

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

	bool insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			_root->_col = BLACK;
			return true;
		}
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = cur->left;
			}
			else if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}
		cur = new Node(kv);
		if (parent->_kv.first > kv.first)
		{
			parent->_left = cur;
		}
		else if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
		}
		cur->_parent = parent;
	}
private:
	Node* _root;
};

新增节点的默认颜色为红色

如果此时插入30,新增的节点我们设置为红色,违反规则3

如果此时插入30,新增的节点我们设置为黑色,违反规则4

如果新增颜色为黑色,违反规则4,整棵树每个路径的颜色节点也都需要跟着改变,直到满足规则4,消耗巨大

如果新增颜色为红色,违反规则3,只有父亲路径颜色被影响,可以接受

插入后如果规则被影响,有两个处理规则:

1.变色(能变色就变色,不能变色再旋转+变色)

2.旋转

实例举例-处理过程

我们需要标记4个节点,新插入节点称为cur,父节点称为parent,父节点的父节点称为grandfather,父节点的兄弟节点称为uncle(为grandfather的另一个子节点)

.

插入时,如果父亲为黑色不用处理,结束(插入后已经是红黑树)。

插入时,如果父亲为红色需要处理,先把父亲变黑(否则违反规则3),继续下一步

.

遇事不决看叔叔,此时分三种情况:

此时祖父一定是黑,原因在于:插入后父亲是红,祖父一定是黑。插入后父亲是黑,看过程1,不需要看叔叔直接结束

1.插入后如果叔叔存在且为红:把叔叔变黑,祖父变红。

如果祖父为根,把根变黑,结束。

否则继续下一步

2. 插入后如果叔叔存在且为黑

3.插入后如果叔叔不存在

.

如果还是子树一部分,把祖父变为cur(虽然不是新增,但是从黑变成红)此时整棵树黑色节点数量继续保持不变,不违反规则4,继续向上调整颜色

如果父亲为红,重复第二步。如果父亲为黑,不违反规则,结束。

继续重复二,三步,直到不满足条件结束

节点性质总结

1.新节点的默认颜色是红色

2.如果新插入节点的父节点颜色是黑色,没有违反红黑树任何性质,则不需要调整,直接结束;

3.如果新插入节点的父节点颜色为红色,违反了性质3不能有连续的红色,需要向上继续调整

4.祖父一定是黑,原因:插入后如果父亲是红,祖父一定是黑。插入后如果父亲是黑,不需要找祖父

这里代称:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点

而此时,cur为红,p为红,g为黑情况已经固定,只需要看u即可

我们可以通过检测新节点插入后,红黑树的性质是否造到破坏来维护红黑树:

情况一: cur为红,p为红,g为黑,u存在且为红

 cur为红,p为红,g为黑,u存在且为红隐含意思:最长路径并没有超过最短路径2倍,不需要变色

有可能是子树,也有可能是整棵树的抽象图,新增可以在a/b任意位置

a/b/c/d/e子树有无数种情况排列存在,cur有可能是新增,也有可能是由其他情况变成红色。处理结果都是p,u变黑,g变红(如果g是子树,变红保持子树黑色节点数量不变,不违反第四点)。把g当成curg不是根,继续向上调整;g是根,在把g变为黑色

情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑(单旋+变色)

孩子与父亲呈直线状态

如果u不存在, cur就是新增。原因在于:如果cur不是新增节点,则cur和p之间一定有一个为黑色,则不满足性质4:每条路径黑色节点数量相同。此时单纯变色无法满足红黑树性质

u不存在

解决方法:对g位置右单旋+p变黑,g变红

如果u存在且为黑色,则此时的cur是红色的原因可能是下面的子树在进行变色处理时,将cur从原本的黑色变为了红色。(否则不满足路径黑节点数量相同的性质)如果只变色p为黑色,左子树比右子树多一个黑色节点,不满足性质4,此时单纯变色无法满足红黑树性质,需要旋转+变色

解决方法:对g位置右单旋+p变黑,g变红

和AVL树一样,因为父亲和孩子呈直线状态,所以只需要单旋即可。
如果p是g的左孩子,cur是p的左孩子,此时g进行右单旋
如果p是g的右孩子,cur是p的右孩子,此时g进行左单旋。

情况三: cur为红,p为红,g为黑,u不存在/u存在且为黑(双旋+变色)

孩子与父亲呈折线状态,新增节点在b/c中

如果u存在且为黑色,新增节点在b/c中,则此时的cur是红色的原因可能是下面的子树在进行变色处理时,将cur从原本的黑色变为了红色。(否则不满足路径黑节点数量相同的性质)单纯变色无法满足红黑树性质,需要双旋+变色(cur变黑,g变红)

如果p是g的左孩子,cur是p的右孩子,p为轴点此时进行左单旋,g再进行右单旋。
如果p是g的右孩子,cur是p的左孩子,p为轴点此时进行右单旋,g再进行左单旋。

双旋之后,cur变为黑色,parent和grandparent为红色。

insert代码实现

其中左右单旋介绍在此博客数据结构【AVL树】_北方留意尘的博客-CSDN博客

enum Color
{
	RED,
	BLACK
};

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

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

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

	bool insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			_root->_col = BLACK;
			return true;
		}
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}
		cur = new Node(kv);
		cur->_col = RED;//破坏规则3
		if (parent->_kv.first > kv.first)
		{
			parent->_left = cur;
		}
		else if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
		}
		cur->_parent = parent;

		//情况一可能持续往上更新,是根才结束,所以用while
		while (parent && parent->_col == RED)
		{
			Node* grandfather = parent->_parent;
			assert(grandfather && grandfather->_col == BLACK);
			//关键看叔叔
			if (grandfather->_left == parent)
			{
				Node* uncle = grandfather->_right;
				//情况一不关注左右,因为不需要旋转
				if (uncle && uncle->_col == RED)
				{
					uncle->_col = BLACK;
					parent->_col = BLACK;
					grandfather->_col = RED;
					//变色后继续向上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				//情况二+三(uncle不存在 or 存在为黑)
				else
				{
					if (cur == parent->_left)//情况二,右单旋+变色,因为父亲也是祖父的左,呈直线
						//    g
						//  p    u 
						//c
					{
						RotaR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED; 
					}
					else if (cur == parent->_right)
					//情况三,p是g的左孩子,cur是p的右孩子,p为轴点此时进行左单旋,cur再进行右单旋
					{
						RotaL(parent);
						RotaR(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break;//不需要判断了
				}
			}
			else//(grandfather->_right == parent)
			{
				Node* uncle = grandfather->_left;
				if (uncle && uncle->_col == RED)
				{
					uncle->_col = BLACK;
					parent->_col = BLACK;
					grandfather->_col = RED;
					//变色后继续向上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					//    g
					//  u    p 
					//         c
					if (cur == parent->_right)//情况二,左单旋+变色,因为父亲也是祖父的右,呈直线
					{
						RotaL(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else if (cur == parent->_left)
						//情况三,p是g的右孩子,cur是p的左孩子,p为轴点此时进行右单旋,g再进行左单旋。
					{
						RotaR(parent);
						RotaL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break;//不需要判断了
				}
			}
		}
		//如果parent不存在,说明grandfather是根
		_root->_col = BLACK;
		return true;
	
	}
	void InOrder()
	{
		PrintBST(_root);
		cout << endl;
	}

private:

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

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

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

		Node* pparent = parent->_parent;
		parent->_right = subRL;//1
		if (subRL != nullptr)
		{
			subRL->_parent = parent;//2
		}
		subR->_left = parent;//3
		subR->_parent = pparent;//4
		parent->_parent = subR;//5
		if (pparent)
		{
			if (pparent->_left == parent)
			{
				pparent->_left = subR;//6
			}
			else
			{
				pparent->_right = subR;//6
			}
		}
		else//root == nullptr,此时subR为根
		{
			_root = subR;//6
		}
	}

	void RotaR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		Node* pparent = parent->_parent;
		parent->_left = subLR;//1
		if (subLR)
			subLR->_parent = parent;//2
		subL->_right = parent;//3
		parent->_parent = subL;//4
		subL->_parent = pparent;//5
		if (pparent)
		{
			if (pparent->_left == parent)
			{
				pparent->_left = subL;//6
			}
			else
			{
				pparent->_right = subL;//6
			}
		}
		else
		{
			_root = subL;//6
		}
	}
	Node* _root = nullptr;
};

验证是否为红黑树

 验证其是否为红黑树,只需要对5条性质都验证即可,违反任意规则都不是红黑树

	bool IsBalance()
	{
		if (_root == nullptr)
			return true;

		if (_root->_col == RED)//检查规则2
		{
			cout << "根节点不是黑色" << endl;
			return false;
		}
		Node* cur = _root;
		int benchmark = 0;//基准值,一条最左路径的黑色节点数量和所有路径黑色节点都相等
		while (cur)
		{
			if(cur->_col == BLACK)
				benchmark++;

			cur = cur->_left;
		}
		

		return _IsBalance(_root,0,benchmark);//检查规则4
	}

private:

	bool _IsBalance(Node* root,int balance,int benchmark)
	{
		if (root == nullptr)
		{
			if (benchmark == balance)//查看是否违反规则4
			{
				return true;
			}
			cout << "每个路径黑色节点数量不同" << endl;
			return false;
		}
		if (root->_col == BLACK)
			++balance; 
		
		if (root->_col == RED && root->_parent->_col == RED)//查看是否违反规则3,反着查
		{
			cout << "不能有连续红节点" << endl;
			return false;
		}
		return _IsBalance(root->_left, balance, benchmark) 
        && _IsBalance(root->_right, balance, benchmark);
		//返回true/false的是给上一层的
		
	}

源码链接

刷题+代码: 刷题+代码 - Gitee.com

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

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

相关文章

基于web的电子图书管理系统

目  录 中文摘要&#xff08;关键词&#xff09; 1 英文摘要&#xff08;关键词&#xff09; 1 前 言 2 1概述 3 1.1系统研究背景 3 1.2系统研究意义 3 2 需求分析 4 2.1可行性分析 4 2.2功能需求分析 4 2.3非功能需求分析 5 3系统分析 6 3.1系统业务流程分析 6 3.2系统数据…

web前端课程设计 HTML+CSS+JavaScript旅游风景云南城市网页设计与实现 web前端课程设计代码 web课程设计 HTML网页制作代码

&#x1f468;‍&#x1f393;静态网站的编写主要是用 HTML DⅣV CSSJS等来完成页面的排版设计&#x1f469;‍&#x1f393;&#xff0c;一般的网页作业需要融入以下知识点&#xff1a;div布局、浮动定位、高级css、表格、表单及验证、js轮播图、音频视频Fash的应用、uli、下拉…

狂神说多线程学习笔记

一、线程简介 1、多任务 现实中太多这样同时做多件事情的例子了&#xff0c;看起来是多个任务都在做&#xff0c;其实本质上我们的大脑在同一时间依旧只做了一件事情。 2、多线程 原来是一条路&#xff0c;慢慢因为车太多了&#xff0c;道路阻塞&#xff0c;效率极低。为了提…

【信管2.6】项目可研(二)详细可行性研究

项目可研&#xff08;二&#xff09;详细可行性研究在实际的整个项目可研的过程中&#xff0c;机会研究和初步可行性研究有可能不会出现&#xff0c;但详细可行性研究这个步骤是不可缺少的。比如说一些升级改造的项目可能需要初步和详细的可行性研究&#xff0c;而一些小项目可…

阿里P8大牛带你深入理解SpringCloud微服务构建文档

前言 蓦然回首自己做开发已经十年了&#xff0c;这十年中我获得了很多&#xff0c;技术能力、培训、出国、大公司的经历&#xff0c;还有很多很好的朋友。但再仔细一想&#xff0c;这十年中我至少浪费了五年时间&#xff0c;这五年可以足够让自己成长为一个优秀的程序员&#…

嵌入式开发学习之--RCC(下)

文章目录前言一、使用HSE二、使用HSI三、代码编写总结前言 这一篇记录一下时钟的具体实验。 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、使用HSE 一般情况下&#xff0c;我们都是使用 HSE&#xff0c;然后 HSE 经过 PLL 倍频之后作为系统时钟…

WRF模式、WRF-SOLAR、WRF-UCM、人工智能气象、FLEXPART、CMIP6数据处理、LEAP模型

1、《高精度气象模拟软件 WRF 实践技术及案例应用》 时间&#xff1a;12月17-18日、24-25日、31日 2、《双碳目标下太阳辐射预报模式【WRF-SOLAR】及改进技术在气象、农林、电力等相关领域中的实践应用 》 时间&#xff1a;12月10-11日、17日-18日 3、《第三期Python人工智能在…

TGK-Planner无人机运动规划算法解读

高速移动无人机的在线路径规划一直是学界当前研究的难点&#xff0c;引起了大量机器人行业的研究人员与工程师的关注。然而无人机的计算资源有限&#xff0c;要在短时间内规划出一条安全可执行的路径&#xff0c;这就要求无人机的运动规划算法必须轻型而有效。本文将介绍一种无…

Java项目:SSM企业OA管理系统

作者主页&#xff1a;源码空间站2022 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文末获取源码 项目介绍 本项目包含管理员与普通员工两种角色&#xff0c; 管理员角色包含以下功能&#xff1a; 岗位管理,部门管理,工龄奖金管理,员工管理,考勤管理,…

Linux下Jenkins服务器安装与使用

CentOS7环境下安装Jenkins​ JDK安装详细见&#xff1a; JDK安装详细步骤 ​ jenkins安装 Jenkins源添加 **注意&#xff1a; ** 问题1、在添加Jenkins源时会出现以下错误 这是由于没有安装wget软件包的原因 进行wget软件包的安装&#xff1a; yum -y install wget 问题2…

HTML网页设计制作——初音动漫(6页) dreamweaver作业静态HTML网页设计模板

HTML实例网页代码, 本实例适合于初学HTML的同学。该实例里面有设置了css的样式设置&#xff0c;有div的样式格局&#xff0c;这个实例比较全面&#xff0c;有助于同学的学习,本文将介绍如何通过从头开始设计个人网站并将其转换为代码的过程来实践设计。 精彩专栏推荐&#x1f4…

市面上跑步耳机哪种好、2023年最适合跑步用的耳机排名

这几年&#xff0c;越来越多人注意到了身体健康的重要性&#xff0c;而随着今年飞盘、露营、刘畊宏女孩的兴起&#xff0c;再到卡塔尔世界杯&#xff0c;不断刺激大众运动、健身的热情&#xff0c;面对全民运动热潮&#xff0c;作为普通人应该如何保持激情&#xff0c;实现身心…

Pr:导出设置之编码设置

视频 VIDEO设置因所选导出格式而异。每种格式都有独特的要求&#xff0c;这些要求决定了哪些设置可用。以导出 H.264 文件格式为例&#xff0c;下面给出有关编码设置 Encoding Settings的选项及说明。性能Performance--硬件加速Hardware Encoding利用系统的可用 GPU 硬件&#…

前后端分离模式下,SpringBoot + CAS 单点登录实现方案

1.CAS服务端构建 1.1.war包部署 cas5.3版本 https://github.com/apereo/cas-overlay-template 构建完成后将war包部署到tomcat即可 1.2.配置文件修改 支持http协议 修改apache-tomcat-8.5.53\webapps\cas\WEB-INF\classes\services目录下的HTTPSandIMAPS-10000001.json&…

PDF文档一键自动生成目录和书签

在工作中经常会遇到编写文档的时候&#xff0c;当我们在word编写完文档后&#xff0c;一般可以自动生成一个目录。为了方便阅读和保护文档不被破坏&#xff0c;一般发送给别人的时候&#xff0c;需要把word文档转换成PDF格式。但是word文档转换为PDF格式后&#xff0c;目录虽然…

【强化学习论文合集】五.2017国际表征学习大会论文(ICLR2017)

强化学习(Reinforcement Learning, RL),又称再励学习、评价学习或增强学习,是机器学习的范式和方法论之一,用于描述和解决智能体(agent)在与环境的交互过程中通过学习策略以达成回报最大化或实现特定目标的问题。 本专栏整理了近几年国际顶级会议中,涉及强化学习(Rein…

vscode electron安装环境

1. 安装nodejs Node.js 安装18.12.1LTS版本 安装完成后确认 node –-version 2. 安装electron npm install electron –g 验证是否安装成功 electron –v 没成功&#xff01;&#xff01;&#xff01; 找解决方案 ​​​​​​​ 无法加载文件xxx.ps1&#xff0c;因为在…

信而泰OLT使用介绍-网络测试仪实操

一、OLT产品介绍 1.概述 PON作为FTTX网络发展的核心技术&#xff0c;局端设备OLT尤其重要。 本文档中主要介绍OLT的功能特性、业务配置 2.基本功能特性 2.1大容量和高集成度 ZXA10 C300集光接入、数据交换、路由处理于一体&#xff0c;提高了系统的集成度。 2.2 EPON功能…

ADSP-21489的图形化编程详解(3:音效开发例程-直通三个例程讲清楚)

Fireware 烧写好了之后&#xff0c;SigmaStudio 图形化开发的基本条件就达成了。我们重新来链接一下硬件&#xff0c;进入图形化编程的阶段&#xff0c;这个阶段我尽量多写一些例程&#xff0c;让大家能够尽快熟悉这个软件开发的全过程。 1. 直通&#xff08;1&#xff09; 1&…