【1++的数据结构】之map与set(二)

news2024/11/25 18:44:26

👍作者主页:进击的1++
🤩 专栏链接:【1++的数据结构】


文章目录

  • 一,前言
  • 二,红黑树的概念及其性质
  • 三,红黑树的插入
  • 四,红黑树的验证
  • 五,map与set的封装
    • 红黑树迭代器的实现
    • map重载[ ]
    • map的封装代码
    • set的封装代码

一,前言

为什么在这里要讲解红黑树?因为map与set的底层是红黑树,因此在这一节我们要讲红黑树的结构,之后会讲以红黑树为 底层的map与set的封装。

二,红黑树的概念及其性质

什么是红黑树?
红黑树是一种平衡二叉树,其结点结构中多了表示颜色的成员变量(红或黑),红黑树通过结点间颜色匹配的限制,从而能够控制树的最长路径不会超过最短路径的2倍。

红黑树的性质:

  1. 每个结点不是黑色就是红色
  2. 根节点是黑色的
  3. 红节点的两个孩子都是黑色的
  4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,黑色结点数目相同
  5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)
    为什么有了上述的几个条件就可以保证最长路径不超过最短路径的二倍呢?
    由于规则三和规则四的限制,导致我们的最长路径一定是一红一黑结点搭配的;而最短结点则是全黑,且由于个规则四,黑色结点数目相同,因此最长路径就不会超过最短路径的二倍了。
    在这里插入图片描述
    红黑树的结点结构与AVL树的结点结构不同之处在于将AVL树结点中的平衡因子变量换为了表示颜色的变量。
enum Colour
{
	Red,
	Black
};

template<class T>
struct TreeNode
{
	TreeNode<T>* _parent;
	TreeNode<T>* _left;
	TreeNode<T>* _right;
	Colour _col;
	T _data;

	TreeNode(const T& data)
		:_parent(nullptr)
		,_left(nullptr)
		,_right(nullptr)
		,_col(Red)
		,_data(data)
	{}
	
};

并且,我们注意到红黑树新结点的默认颜色是红色?为什么要进行这样的设计呢?
通过观察红黑树的几条性质我们发现,当其默认结点颜色为黑色时,由于规则四,其新结点对这棵树一定会产生影响,而若新节点颜色为红色,则影响会比较小。
在这里插入图片描述

三,红黑树的插入

按照二叉搜索树的插入规则插入这部分我们在前面已经多次讲到,因此本节将不再讲解,最重要的还是其规则被破坏后,进行平衡的这一部分。
由于新插入结点的颜色默认为红色,若其双亲结点为黑色,则不违反任何规则,若其双亲结点为红色,则违反了规则三,需要进行调整。已经有人为我们总结几种调整的情况,我们只需要按照其总结进行调整就行。
几种需要调整的情况如下:

  1. 情况一:在这里插入图片描述
    当cur,p,u为红,g为黑时
    调整方式:将p,u改为黑,g改为红。
    若g为根节点,则需将根节点(g)改为黑色;
    若不是,且g的双亲结点也为红色,则按此方式继续向上调整。直到调整完成或是到达根节点。(注:根节点都得调整为黑色)。

代码如下:

while (parent && parent->_col == Red)
		{
			Node* grandparent = parent->_parent;
			assert(grandparent);
			assert(grandparent->_col == Black);
			if (grandparent->_left == parent)
			{
				Node* uncle = grandparent->_right;
				//情况一
				if (uncle && uncle->_col == Red)
				{
					parent->_col=uncle->_col = Black;
					grandparent->_col = Red;
					cur = grandparent;
					parent = grandparent->_parent;		
			else
			{
				Node* uncle = grandparent->_left;
				//情况一
				if (uncle && uncle->_col == Red)
				{
					parent->_col = uncle->_col = Black;
					grandparent->_col = Red;
					cur = grandparent;
					parent = grandparent->_parent;
				}		
			}

		}

		_root->_col = Black;
		return make_pair(_iterator(cur), true);


	}
  1. 情况二:
    在这里插入图片描述
    若cur,p为红,u为黑或者不存在时。

u的情况说明:
若u不存在,则cur一定是新插入的结点,若其不是新插入的结点,则p与cur一定有一个黑色结点,这不符合规则四。
若u存在,则cur一定不是新插入的结点,且原来为黑色,当新插入一个节点后调整为了红色。

若g的左子树为p,p的左子树为cur:调整方式,针对g做右单旋,并且将g改为红色,p改为黑色。
若g的右子树为p,p的右子树为cur:调整方式,针对做左单旋,并且将g改为红色,p改为黑色。
3. 情况三:
在这里插入图片描述
若g的左子树为p,p的右子树为cur,则先针对p做左旋,就和情况二相同了,再进行右旋。
若g的右子树为p,p的左子树为cur,则先针对p做右旋,就和情况二相同了,再进行左旋。

代码如下:

while (parent && parent->_col == Red)
		{
			Node* grandparent = parent->_parent;
			assert(grandparent);
			assert(grandparent->_col == Black);
			if (grandparent->_left == parent)
			{
				Node* uncle = grandparent->_right;
				//情况一
				if (uncle && uncle->_col == Red)
				{
					parent->_col=uncle->_col = Black;
					grandparent->_col = Red;
					cur = grandparent;
					parent = grandparent->_parent;

				}
				else//情况二+三
				{
					if (parent->_left == cur)
					{
						RotateR(grandparent);
						grandparent->_col = Red;
						parent->_col = Black;
					}
					else
					{
						RotateL(parent);
						RotateR(grandparent);
						grandparent->_col = Red;
						cur->_col = Black;

					}

					break;

				}
				
			}
			else
			{
				Node* uncle = grandparent->_left;
				//情况一
				if (uncle && uncle->_col == Red)
				{
					parent->_col = uncle->_col = Black;
					grandparent->_col = Red;
					cur = grandparent;
					parent = grandparent->_parent;

				}
				else//情况二+三
				{
					if (parent->_right == cur)
					{
						RotateL(grandparent);
						grandparent->_col = Red;
						parent->_col = Black;
					}
					else
					{
						RotateR(parent);
						RotateL(grandparent);
						grandparent->_col = Red;
						cur->_col = Black;

					}
					break;
				}
			

			}


		}

		_root->_col = Black;
		return make_pair(_iterator(cur), true);
	}

四,红黑树的验证

我们通过验证红黑树的几条性质来验证这棵树是否为红黑树。

  1. 根节点为黑色结点
  2. 各路径的黑色结点数相同
  3. 红色结点的子节点为黑色
    接下来,我们根据这几条规则来写代码判断是否为红黑树
    代码如下:
bool Isbalance() 
	{
		if (_root == nullptr)
		{
			return true;
		}

		if (_root->_col == Red)
		{
			cout << "根不是黑色结点" << endl;
			return false;
		}

		int BenchNum = 0;
		return PrevCheck(_root,0,BenchNum);

	}

bool PrevCheck(Node* root, int BlackNum, int& BenchNum)
	{
		if (root == nullptr)
		{
			if (BenchNum == 0)
			{
				BenchNum = BlackNum;
				return true;
			}
			else
			{
				if (BenchNum != BlackNum)
				{
					cout << "路径黑色结点数量不同" << endl;
					return false;
				}
				else
					return true;
			}
		}

		if (root->_col == Black)
		{
			BlackNum++;
		}
		if (root->_col == Red && root->_parent->_col == Red)
		{
			cout << "连续两个红色" << endl;
			return false;
		}

		return PrevCheck(root->_left, BlackNum, BenchNum) 
		&& PrevCheck(root->_right, BlackNum, BenchNum);
	}

五,map与set的封装

map与set的底层都是红黑树,那么如何用同一种底层结构去实现封装两个不同的数据结构呢?
事实上,在红黑树中其类模板为template<class K,class
T>。在map中T为pair<K,V>类型,在set中为K类型,这样就实现了同一底层封装两种不同的数据结构,也就是泛型编程。

红黑树迭代器的实现

红黑树是根据某种规则将一些结点链接起来的结构。因此其迭代器底层也与链表的迭代器一样,是结点指针,其重点是++和–的运算符重载。
通过观察,我们对++的重载有如下总结:平衡二叉搜索树按照中序遍历则是有序的。因此,对于++,则是找中序遍历的下一个结点。中序遍历的规则:左子树–根–右子树。
要找当前结点的下一个,则当其右子树不为空时,其右子树的最左边的结点就是下一个结点。
当右子树为空时,则向上找,直到cur!=parent->right,或parent为空其下一结点就为双亲结点。

代码如下:

Self& operator++()
	{
		if (_node->_right)
		{
			Node* left = _node->_right;
			while (left->_left)
			{
				left = left->_left;
			}
			_node = left;
		}
		else
		{
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && parent->_right == cur)
			{
				cur = cur->_parent;
				parent = cur->_parent;
			}
			_node = parent;

		}
		return *this;

	}

–的重载与++相反,就不再详细讲解。
代码如下:

Self& operator--()
	{
		if (_node->_left)
		{
			Node* right = _node->_left;
			while (right->_right)
			{
				right = right->_right;
			}
			_node = right;
		}
		else
		{
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && parent->_left == cur)
			{
				cur = cur->_parent;
				parent = cur->_parent;
			}
			_node = parent;

		}
		return *this;
	}

由于在插入时,map与set传入的类型不同,但都需要比较K类型对象,因此,我们在map与set中都写一个用于确定比较元素的类型的仿函数,map中由于T为pair<K,V>,(pair<K,V> kv)则需比较的是kv.first。
而set中的T为K,K key就直接比较key就行。

struct KeyOf
		{
			const K& operator() (const pair<K, V>& kv)
			{
				return kv.first;
			}
		};



struct KeyOfS
		{
			const K& operator()(const K& key)
			{
				return key;

			}
		};

map重载[ ]

map的[ ]功能比较强大,它集查找,插入,修改功能为一体。

V& operator[](const K& key)
		{
		pair<iterator, bool> ret = Insert(make_pair(key, V()));
			return ret.first->second;
		}

若树种有这个结点,则会插入失败,会返回这个结点的迭代器以及false。
在这里插入图片描述
由于重载函数返回的是V的引用,因此此结点的value便可以被修改。
若此结点不存在,则会插入一个V的匿名对象的value值。并返回其引用。

map的封装代码

以下是map的封装代码:

template<class K,class V>
	class map
	{
		struct KeyOf
		{
			const K& operator() (const pair<K, V>& kv)
			{
				return kv.first;
			}
		};

	public:
		typedef  typename RBTree<K, pair<K, V>, KeyOf>::_iterator iterator;

		pair<iterator, bool> Insert(const pair<K, V>& kv)
		{
			return _t.Insert(kv);
		}

		V& operator[](const K& key)
		{
			pair<iterator, bool> ret = Insert(make_pair(key, V()));
			return ret.first->second;
		}

		iterator begin()
		{
			return _t.begin();
		}

		iterator end()
		{
			return _t.end();
		}

		
	private:
		RBTree<K,pair<K,V>, KeyOf> _t;
	};

set的封装代码

以下是set的封装代码:

template<class K>
	class set
	{
		struct KeyOfS
		{
			const K& operator()(const K& key)
			{
				return key;

			}
		};

	public:
		typedef typename RBTree<K, K, KeyOfS>::_iterator iterator;
	
		pair<iterator, bool> Insert(const K& key)
		{
			return _t.Insert(key);

		}

		iterator begin()
		{
			return _t.begin();
		}

		iterator end()
		{
			return _t.end();
		}

	private:
		RBTree< K, K, KeyOfS> _t;

	};

在这里插入图片描述

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

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

相关文章

c语言flag的使用

flag在c语言中标识某种状态或记录某种信息&#xff0c;可以通过修改flag中来控制程序流程,判断某种状态是否存在或记录某种信息 操作:(1)初始化 (2)赋值 (3)判断 (4)修改 (5)去初始化 #include <stdlib.h>int power_state_check;int main() {int i 0;power_state_check…

统计命令汇总

适用于Unix体系 关于wc命令 Word Count 用于统计指定文件中的字节数、字数、行数&#xff0c;并将统计结果显示输出。 wc [-lcw] c 统计字节数 l 统计行数 m 统计字符数&#xff0c;此标志不能与-c标志一起使用 w 统计字数。一个字定义为由空白、跳格或换行字符分隔的字符串 统…

mysql数据库使用技巧整理

查看当前数据库已建立的client连接 > SHOW VARIABLES LIKE max_connections; -- 查看数据库允许的最大连接数&#xff0c;不是实时正在使用的连接数 > SHOW STATUS LIKE Threads_connected; -- 查看当前数据库client的连接数 > SHOW PROCESSLIST; -- 查看具体的连接

界面控件DevExtreme(v23.2)下半年发展路线图

在这篇文章中&#xff0c;我们将介绍DevExtreme在v23.2中发布的一些主要特性&#xff0c;这些特性既适用于DevExtreme JavaScript (Angular、React、Vue、jQuery)&#xff0c;也适用于基于DevExtreme的ASP. NET MVC/Core控件。 DevExtreme包含全面的高性能和响应式UI小部件集合…

论文翻译 : 风廓线对地面机载风能系统性能的影响

目录 摘要1 引言2. 风力条件2.2 风况2.3 风况聚类2.4 聚类分析2.5 聚类统计分析 3 引言 摘要 摘要&#xff1a;本研究通过确定基于现实垂直风速剖面的循环可行的功率优化飞行轨迹&#xff0c;来研究抽水模式地面生成的空中风能系统&#xff08;AWESs&#xff09;的性能。这些1…

基于Springcloud的基础框架,统一gateWay网关鉴权demo,附下载地址

基于Springcloud的基础框架&#xff0c;统一gateWay网关鉴权demo&#xff0c;附下载地址 使用方式&#xff1a; 1、搭建nacos环境&#xff0c;修改对应nacos地址 2、修改mysql地址,导入sql语句 ###框架内容 SpringcloudGatewayJWTNacosFeginmysqlMybatis plus 具体功能 基于…

基于Matlab实现多个图像融合案例(附上源码+数据集)

图像融合是将多幅图像合成为一幅图像的过程&#xff0c;旨在融合不同图像的信息以获得更多的细节和更丰富的视觉效果。在本文中&#xff0c;我们将介绍如何使用Matlab实现图像融合。 文章目录 简单案例源码数据集下载 简单案例 首先&#xff0c;我们需要了解图像融合的两种主…

Linux之修改服务端口号

本次演示以SSH服务为例&#xff0c;SSH默认监听端口是22,先保留了22端口&#xff0c;所以我们要进入ssh的配置文件添加新端口并注释或删掉原有端口。 1、使用vi编辑器修改文件 sshd_config,路径是/etc/ssh/sshd_config,找到“#Port 22”,添加新的端口号10086。 2、如果你关闭了…

2023开学礼中国海洋大学《乡村振兴战略下传统村落文化旅游设计》许少辉新海洋图书馆

2023开学礼中国海洋大学《乡村振兴战略下传统村落文化旅游设计》许少辉新海洋图书馆

建筑结构健康监测系统:智能监测建筑结构健康状况

大型公共建筑因其投资大、结构形式复杂、建设工期长、施工工艺复杂、使用年限长等特点。在使用期间受超常荷载、材料老化、构件缺陷等因素的作用&#xff0c;结构将逐渐产生损伤累积&#xff0c;从而使结构的承载能力降低&#xff0c;抵抗自然灾害的能力下降。如遇地震、台风等…

【解决】uniapp项目集成钉钉H5微应用,控制台提示 ReferenceError: dd is not defined

【问题】 在钉钉APP内集成H5微应用&#xff0c;跳转到内部应用&#xff0c;按照钉钉开放平台文档引入钉钉客户端SDK。 引入成功&#xff0c;运行项目&#xff0c;调用dd.getAuthCode()方法获取免登授权码&#xff0c;控制台显示 “ReferenceError: dd is not defined” 【解决…

计算机组成原理学习记录(更新中)

文章目录 仅做个人记录计组的学习中认为容易记错的点或是个人认为的要点&#xff0c;如有错误&#xff0c;请多包涵。 学习资源为b站网课&#xff1a;王道计算机考研 计算机组成原理 大部分图片来自该网课 &#xff08;1&#xff09;冯诺依曼型计算机由五个部分组成&#xff…

【快应用】快应用与网页通信踩坑合集处理

【关键词】 Web、postMessage、onMessage 【问题背景】 快应用中通过web组件加载的h5网页&#xff0c;快应用在和网页进行通信时&#xff0c;经常会遇到网页发送信息给快应用&#xff0c;快应用成功收到&#xff0c;反过来的时候&#xff0c;h5网页就没法收到了。如提示 xxx …

Spark2x原理剖析(二)

一、概述 基于社区已有的JDBCServer基础上&#xff0c;采用多主实例模式实现了其高可用性方案。集群中支持同时共存多个JDBCServer服务&#xff0c;通过客户端可以随机连接其中的任意一个服务进行业务操作。即使集群中一个或多个JDBCServer服务停止工作&#xff0c;也不影响用…

SSRF服务端请求伪造

服务端请求伪造&#xff0c;其实就是攻击者构造恶意请求&#xff0c;服务端发起恶意请求&#xff0c;如果服务端不对用户传递的参数进行严格的过滤和限制&#xff0c;就可能导致服务端请求伪造 上面是百度识图&#xff0c;我们可以传递图片地址&#xff0c;百度识图向图片发起…

Linux内核源码分析 (7)内核内存布局和堆管理

一、Linux内核内存布局 64位Linux一般使用48位来表示虚拟地址空间&#xff0c;45位表示物理地址。通过命令&#xff1a;cat/proc/cpuinfo。查看Linux内核位数和proc文件系统输出系统软硬件信息如下: lhLH_LINUX:~$ cat /proc/cpuinfo vendor_id : GenuineIntel // CPU制造商 cp…

基于java SpringBoot和Vue uniapp的影楼摄影预约小程序

摘要 今天信息技术的发展很快&#xff0c;其足迹在我们的生活中随处可见。它影响着我们的衣食住行等各种需求。影响也在逐渐增加&#xff0c;逐渐渗透到各行各业&#xff0c;在这种背景下&#xff0c;经过实地考察后&#xff0c;为了让婚纱照管理更加高效方便&#xff0c;我决定…

Java的输入与输出

在Java中&#xff0c;输入和输出是通过标准的输入流&#xff08;System.in&#xff09;和标准的输出流&#xff08;System.out&#xff09;进行处理的。我们可以通过util.Scanner这个类实现输入和输出&#xff0c;以下是一些常用的输入输出函数的介绍 输出&#xff1a; System.…

NFTScan 浏览器正式版上线 2 周年!

NFTScan 成立于 2021 年 4 月份&#xff0c;总部位于香港。在 2021 年的 7 月份&#xff0c;NFTScan 团队对外发布了 NFTScan 浏览器公测版&#xff0c;并在同年的 9 月 4 号&#xff0c;对外发布了 NFTScan 浏览器正式版&#xff0c;同步启用了全球品牌域名&#xff1a;NFTSCA…

深入理解线段树

大家好&#xff0c;我是 方圆。线段树&#xff08;Segment Tree&#xff09; 是常用的维护 区间信息 的数据结构&#xff0c;它可以在 O(logn) 的时间复杂度下实现单点修改、区间修改、区间查询&#xff08;区间求和、区间最大值或区间最小值&#xff09;等操作&#xff0c;常用…