利用红黑树封装map,和set,实现主要功能

news2025/2/24 12:02:51

如果不知道红黑树是什么的时候可以去看看这个红黑树

思路

首先我们可以把封装分为两个层面理解,上层代码就是set,和map,底层就是红黑树
就相当于根据红黑树上面套了两个map,set的壳子,像下面这张图一样
在这里插入图片描述
对于map和set,map里面存放的是pair一个键值对,而set就只是一个key,一般的想法是实现两个红黑树,一个里面的节点放key一个放pair,那么这样就太冗余了,那么我们只用实现一个红黑树就行了,我们利用模板就可以很轻松的解决冗余的问题
下面就来看看怎么具体的实现的

创建set,map

这里的set和map我们统称为myset,mymap
我们首先根据红黑树的基础把set,和map的架子搭起来
先来实现基本的插入功能

# include"RBtree.h"
namespace mymap
{
	template<class key, class value>
	class mymap
	{
	public:
		struct k_of_t_map
		{
			const key& operator()(const pair<key,value>& input_map)
			{
				return input_map.first;
			}
		};
		bool Insert(const pair<key, value>& input_map)
		{
			return _t.insert(input_map);
		}
	private:
		RBtree<key, pair< const key, value>, k_of_t_map> _t;
	};
}

我们在插入的时候是根据搜索树的规则进行插入的,搜索树肯定是要比较大小的,我们的pair他原生是支持比较大小的,但是库里面的比较大小是根据first和second来进行比较的,我们这里肯定不是这样的,我们是想两个pair不同的first进行比较大小的,所以库里面的比较无法满足我们的需求
这里我们的解决方法是利用仿函数

struct k_of_t_map
		{
			const key& operator()(const pair<key,value>& input_map)
			{
				return input_map.first;
			}
		};

我们通过实现一个内部类对operator()的重载,实现类似于函数()的功能,就能像函数一样使用,去实现我们想要的功能,这里我们通过operator()我们直接返回input_map.first,就得到了我们想要的pair的first也就是用来比较大小的key

同样set也是一样的道理
这里的set其实有点冗余了,是为了配合map而创建的,他本身就不需要都行

# include"RBtree.h"
namespace myset
{
	template<class key>
	class myset
	{
	public:

		struct k_of_t_set
		{
			const key& operator()(const key& input_key)
			{
				return input_key;
			}
		};
		bool Insert(const key& input_key)
		{
			return _t.insert(input_key);
		}
	private:
		RBtree< key,const key,k_of_t_set> _t;
	};
}

有了上面的架子之后,我们红黑树的插入功能里面的比较大小的方法,也需要改一下,改成在比较大小的时候去调用mymap,myset里面的内部类里面的成员函数。

所以我们在创建mymap,myset的成员变量时,就要根据RBtree在加上自身的性质来创建,这里mymap和myset最核心的差别就是一个数据类型,另一个是比较大小的逻辑不同,所以我们在RBtree的模板参数就要变成

template<class key, class T, class k_of_t>

多提供一个k_of_t的模板参数 这个就是用来设置比较大小的方法用的
所以我们对于的mymap,myset就要变成

RBtree<key, pair< const key, value>, k_of_t_map> _t;
RBtree< key,const key,k_of_t_set> _t;

然后再插入的比较大小的逻辑也要套用这个内部类的成员函数
首先通过k_of_t来创建一个 kot的对象
然后再根据这个对象去调用相应的成员函数
在这里插入图片描述
关于mymap,myset的成员变量的初始化
其实我们根据代码我们就可以发现mymap,myset的成员函数的初始化其实就是去复用RBtree的初始化
就像一个映射一样,**假设初始化mymap把mymap里面的模板参数传过去,然后再去初始化RBtree的_root。**把相应的数据类型比较大小的方法替换的mymap的比较方法
myset也是同理。
在这里插入图片描述
有了以上的步骤之后我们就可以实现mymap和myset的插入了

map set的迭代器

下面我们讲讲迭代器的实现
我们先来看看迭代器的构造

template<class T,class ref,class ptr>
//迭代器就相当于一个节点的指针
struct RBtreeIterator//默认公有
{
	typedef RBtreenode<T> Node;
	typedef RBtreeIterator<T, ref, ptr> self;

	Node* _it_node;
	RBtreeIterator (Node* input_node, Node* root)
		:_it_node(input_node)
	{}

这里我们传了三个模板参数一个是数据类型一个是引用,最后一个是指针
这样的目的是为了方便我们想要引用的时候用引用,想要指针的时候用指针
这里我们的成员变量是一个RBtreenode类型,我们需要构造一个节点,把他当作一个指针,做为我们迭代器遍历容器的一个媒介迭代器就相当于一个节点的指针
了解了上面的结构之后
下面我们来看看迭代器的operator++和–这个是迭代器的核心,也是比较难的

operator++

先来看看代码

self operator++()
{
	if (_it_node->_right != nullptr)//找右最小节点
	{
		Node* right_min = this->_it_node->_right;
		while (right_min->_left != nullptr)
		{
			right_min = right_min->_left;
		}
		_it_node = right_min;
	}
	else//右边访问完,代表以他为基本的左子树右子树都完了,就要向上走
	{
		Node* cur = _it_node;
		Node* parent = cur->_pre_node;
		//这里要找的是相对于儿子的父亲,父亲的右节点不是孩子的节点
		//如果一直是右节点就代表当前节点的左右子树都完了
		//如果找到父亲的左节点是孩子,就代表父亲还有右节点没访问完。
		while (parent != nullptr && parent->_right == cur)
		{
			cur = parent;
			parent = cur->_pre_node;
		}
		_it_node = parent;
	}
	return *this;
}

这里我们需要和红黑树的性质一起来说一下,我们知道红黑树的中序遍历出来是一个从小到大的有序序列
我们的++也是利用这个思想来的,但是中序遍历是一个看全局思想来的,但我们这里是要看局部,只考虑当前中序局部要访问的下⼀个结点。可以看成一个局部的左根右
为什么不能直接用中序遍历来写红黑树的迭代器
那是因为
随机访问需求 迭代器通常需要支持随机访问操作,这意味着可以在常数时间内访问任意一个元素。而中序遍历是线性的,不支持随机访问。
也不能更加灵活的访问容器,每次都要从根开始
我们现在来具体说一下代码

if (_it_node->_right != nullptr)//找右最小节点
	{
		Node* right_min = this->_it_node->_right;
		while (right_min->_left != nullptr)
		{
			right_min = right_min->_left;
		}
		_it_node = right_min;
	}

这里是找除开当前节点,如果当前节点的右边不为空,我们就要一直找右边的最小节点(也就是最左节点)
为什么要这样找,我们可以联系下面的这个beign()这个函数来看看

typedef RBtreenode<T> node;
typedef RBtreeIterator<T, T&, T*> Iterator;
typedef RBtreeIterator<T, const T&, const T*> ConstIterator;
Iterator begin()
{
	node* cur = _root;
	while (cur != nullptr && cur->_left != nullptr)
	{
		cur = cur->_left;
	}
	return Iterator(cur);
}

这个begin()返回的是一个迭代器类型,他是一直找红黑树的最小节点,找到之后用最小节点去构造一个迭代器类型,当找到最小节点的时候就代表以这个节点为基准他的左子树已经访问完了然后要去看看右子树,我们可以画图看看
在这里插入图片描述
然后我们来看看下面的代码

	else//右边访问完,代表以他为基本的左子树右子树都完了,就要向上走
	{
		Node* cur = _it_node;
		Node* parent = cur->_pre_node;
		//这里要找的是相对于儿子的父亲,父亲的右节点不是孩子的节点
		//如果一直是右节点就代表当前节点的左右子树都完了
		//如果找到父亲的左节点是孩子,就代表父亲还有右节点没访问完。
		while (parent != nullptr && parent->_right == cur)
		{
			cur = parent;
			parent = cur->_pre_node;
		}
		_it_node = parent;
	}
	return *this;
}

当到15之后我们的第一个if进不去就要进else,走到else说明15的左右子树都访问完了,右边为空了,就要向上返回,去看看15的父亲,也就是10的左右子树访问完没有,15是10的右子树代表以10为基础的左右子树都访问完了,又让10去找他的父亲18,看看18的右子树是不是指向10的,但通过图片来看,18的左子树的指向10的,说明18的右子树还没有访问,然后就需要去访问18和18的右子树
在这里插入图片描述
总的来说就一句话:就要去找不是自己父亲的右边是指向自己的节点
下面来说说operator–

operator- -

–的逻辑正好相反,可以看成一个局部的右根左
我们先来看看end()

Iterator end()
{
	return Iterator(nullptr,_root);
}

首先end()代表末尾,我们这里直接用空表示的库里面是有一个哨兵位节点
就像这样
在这里插入图片描述
当然用空也可以实现这个功能
我们只需要特殊处理一下,end我们就想表示红黑树里面的最右节点,所以我们多再operator–里面多添加一种情况就行了,去找他的最右节点,但我们需要多传一个_root的参数进来

self operator--()
{
	//处理特殊情况
	if (this->_it_node == nullptr)
	{
		Node* rightmost = _it_root;
		while (rightmost != nullptr && rightmost->_right != nullptr)
		{
			rightmost = rightmost->_right;
		}
		_it_node = rightmost;
	}//这里和begin()类似
	
	//右根左
	else if (this->_it_node->_left != nullptr)//找左节点的最大节点
	{
		Node* left_max = _it_node->_left;
		while (left_max != nullptr && left_max->_right != nullptr)
		{
			left_max = left_max->_right;
		}

		this->_it_node = left_max;
	}
	else//左边为空代表当前节点为基本的左右子树都访问完了
	{//就要往上面寻找不是父亲节点指向左边的孩子节点,要找父亲节点指向右边是孩子节点的父亲节点
		Node* cur = _it_node;
		Node* parent = cur->_pre_node;
		while (parent != nullptr && parent->_left == cur)
		{
			cur = parent;
			parent = cur->_pre_node;
		}
		_it_node = parent;
	}
	return *this;
}

–就相当于反过来这里就不作过多说明了

迭代器整体代码

还有一些小的函数就不作过多说明了

template<class T,class ref,class ptr>
//迭代器就相当于一个节点的指针
struct RBtreeIterator//默认公有
{
	typedef RBtreenode<T> Node;
	typedef RBtreeIterator<T, ref, ptr> self;

	Node* _it_node;
	Node* _it_root;
	RBtreeIterator (Node* input_node, Node* root)
		:_it_node(input_node)
		, _it_root(root)
	{}
	self operator++()
	{
		if (_it_node->_right != nullptr)//找右最小节点
		{
			Node* right_min = this->_it_node->_right;
			while (right_min->_left != nullptr)
			{
				right_min = right_min->_left;
			}
			_it_node = right_min;
		}
		else//右边访问完,代表以他为基本的左子树右子树都完了,就要向上走
		{
			Node* cur = _it_node;
			Node* parent = cur->_pre_node;
			//这里要找的是相对于儿子的父亲,父亲的右节点不是孩子的节点
			//如果一直是右节点就代表当前节点的左右子树都完了
			//如果找到父亲的左节点是孩子,就代表父亲还有右节点没访问完。
			while (parent != nullptr && parent->_right == cur)
			{
				cur = parent;
				parent = cur->_pre_node;
			}
			_it_node = parent;
		}
		return *this;
	}
	self operator--()
	{
		//处理特殊情况
		if (this->_it_node == nullptr)
		{
			Node* rightmost = _it_root;
			while (rightmost != nullptr && rightmost->_right != nullptr)
			{
				rightmost = rightmost->_right;
			}
			_it_node = rightmost;
		}
		//右根左
		else if (this->_it_node->_left != nullptr)//找左节点的最大节点
		{
			Node* left_max = _it_node->_left;
			while (left_max != nullptr && left_max->_right != nullptr)
			{
				left_max = left_max->_right;
			}

			this->_it_node = left_max;
		}
		else//左边为空代表当前节点为基本的左右子树都访问完了
		{//就要往上面寻找不是父亲节点指向左边的孩子节点,要找父亲节点指向右边是孩子节点的父亲节点
			Node* cur = _it_node;
			Node* parent = cur->_pre_node;
			while (parent != nullptr && parent->_left == cur)
			{
				cur = parent;
				parent = cur->_pre_node;
			}
			_it_node = parent;
		}
		return *this;
	}
	bool operator==(const self& input) const
	{
		return input._it_node == _it_node;
	}
	bool operator!=(const self& input) const
	{
		return input._it_node != _it_node;
	}
	ref operator*()
	{
		return this->_it_node->_data;
	}
	ptr operator->()
	{
		return &this->_it_node->_data;
	}
};

operator[]

这个是map比较有特色的一个功能,他支持插入,查询和修改,我们来看看是怎么实现的
来看看代码

value& operator[](const key& input_key)   // int 1   0 
{                                         //key    value
	pair<iterator, bool> ret = Insert({ input_key,value() });
	iterator it = ret.first;
	return it->second;
}

首先要实现这个功能我们的插入的返回值需要修改一下
需要返回一个键值对
在这里插入图片描述
然后我们再来分析一下
首先我们用一个键值对ret来接受插入的返回值,假设插入10,(红黑树里面没有10),然后再插入一个value()的一个缺省值,相当于一个默认构造,int的就是0,我把这个插入的默认值理解成一个占位符 插入成功返回true,然后我们取这个返回值的first,也就是一个迭代器,然后这个迭代器也是pair类型的,我们再返回他的second,也就是返回里面存放的数据,然后就可以插入了,也就相当于it->second = “123”
在这里插入图片描述
那是怎么支持查找和修改的呢
当我们再次插入10时,insert就会返回一个false,然后同样返回一个迭代器
在这里插入图片描述

然后去取他的first再取second就可以完成修改了
这样就实现了operator[]的功能
以上就是利用红黑树封装map,和set,实现主要功能,有什么问题欢迎指正

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

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

相关文章

电子应用设计方案-39:人工智能系统方案设计

人工智能系统方案设计 一、引言 随着人工智能技术的快速发展&#xff0c;越来越多的领域开始应用人工智能系统来解决复杂的问题和实现智能化的任务。本方案旨在设计一个通用的人工智能系统框架&#xff0c;以满足不同业务需求和应用场景。 二、系统概述 1. 系统目标 - 提供高…

Unity 设计模式-策略模式(Strategy Pattern)详解

策略模式&#xff08;Strategy Pattern&#xff09;是一种行为型设计模式&#xff0c;定义了一系列算法&#xff0c;并将每种算法封装到独立的类中&#xff0c;使得它们可以互相替换。策略模式让算法可以在不影响客户端的情况下独立变化&#xff0c;客户端通过与这些策略对象进…

你还没有将 Siri 接入GPT对话功能吗?

由于各种原因&#xff0c;国内ios用户目前无缘自带 AI 功能&#xff0c;但是这并不代表国内 ios 无法接入 AI 功能&#xff0c;接下来手把手带你为iPhone siri 接入 gpt 对话功能。 siri 接入 chatGPT 暂时还无法下载 ChatGPT app&#xff0c;或者没有账号的读者可以直接跳到…

linux运维命令

防火墙相关命令 防火墙规则查看 firewall-cmd --list-all 禁ping firewall-cmd --permanent --add-rich-rulerule protocol valueicmp drop firewall-cmd --reload 执行完以上命令后&#xff0c;通过firewall-cmd --list-all查看规则生效情况 firewall-cmd --list-all 其…

矩阵乘法        ‌‍‎‏

矩阵乘法 C语言代码C 语言代码Java语言代码Python语言代码 &#x1f490;The Begin&#x1f490;点点关注&#xff0c;收藏不迷路&#x1f490; 计算两个矩阵的乘法&#xff1a; 设有矩阵(A)为(nm)阶矩阵&#xff0c;矩阵(B)为(mk)阶矩阵&#xff0c;二者相乘得到的矩阵(C)是(…

docker更换容器存储位置

一&#xff1a;原因 今天之前在某个服务器上使用docker搭建的服务突然无法访问了&#xff0c;进入服务器查看发现服务运行正常&#xff0c;但是就是无法使用&#xff0c;然后我这边准备将docker服务重新启动下看看&#xff0c;发现docker服务无法重启&#xff0c;提示内存已满…

11.10VSCode配置 SSH连接远程服务器+免密连接教程

Jk200497 VScode通过remote ssh连接虚拟机 & 报错 过程试图写入的管道不存在&#xff08;已解决&#xff09;_连接虚拟机之后,提示管道错误,把上述路径加入到扩展中-CSDN博客 VSCode配置 SSH连接远程服务器免密连接教程

汽车EEA架构:架构的简介

1.架构的定义 汽车领域谈论的架构一词&#xff0c;来源于英文单词Architecture。在《系统架构:复杂系统的产品设计与开发》一书中对架构的定义如下:系统架构是一种概念的具象化&#xff0c;是物理或信息功能到形式元素的分配&#xff0c;是系统之内的元素之间的关系与周边环境…

Nginx下载、安装、启动及常用命令

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

Mac快速安装 chromedriver驱动

全篇大概1200字&#xff08;含代码&#xff09;&#xff0c;建议阅读时间5分钟。 什么是chromedriver&#xff1f; ChromeDriver 充当了 Selenium WebDriver 和 Chrome 浏览器之间的桥梁&#xff0c;允许开发者通过编程控制浏览器进行自动化测试或操作。 一、下载chromedriver…

大数据项目-Django基于聚类算法实现的房屋售房数据分析及可视化系统

《[含文档PPT源码等]精品Django基于聚类算法实现的房屋售房数据分析及可视化系统》该项目含有源码、文档、PPT、配套开发软件、软件安装教程课程答疑等&#xff01; 数据库管理工具&#xff1a;phpstudy/Navicat或者phpstudy/sqlyog 后台管理系统涉及技术&#xff1a; 后台使…

2024前端框架年度总结报告(二):新生qwik+solid和次新生svelte+Astro对比 -各自盯着前端的哪些个痛点 - 前端的区域发展差异

引言 2024年&#xff0c;前端开发依然是技术领域的热点之一。随着 Web 应用的日益复杂&#xff0c;前端框架的更新换代也加速了。尽管 React、Vue 和 Angular 老牌框架年度总结 等“老牌”框架仍然占据着主流市场&#xff0c;但一些新兴的框架在不断挑战这些“巨头”的地位&am…

MBox20边缘计算网关:氢能车间数据采集的智慧引擎

氢能作为未来能源体系的重要组成部分&#xff0c;其安全、高效、环保的特性备受瞩目。在氢能车间的日常运营中&#xff0c;数据采集是确保生产流程优化、设备稳定运行及能效提升的关键环节。然而&#xff0c;面对氢能车间复杂多变的生产环境和海量数据&#xff0c;如何实现高效…

【Linux篇】权限管理 - 用户与组权限详解

一. 什么是权限&#xff1f; 首先权限是限制人的。人 真实的人 身份角色 权限 角色 事物属性 二. 认识人–用户 Linux下的用户分为超级用户和普通用户 root :超级管理员&#xff0c;几乎不受权限的约束普通用户 :受权限的约束超级用户的命令提示符是#&#xff0c;普通用…

GPS模块/SATES-ST91Z8LR:电路搭建;直接用电脑的USB转串口进行通讯;模组上报定位数据转换地图识别的坐标手动查询地图位置

从事嵌入式单片机的工作算是符合我个人兴趣爱好的,当面对一个新的芯片我即想把芯片尽快搞懂完成项目赚钱,也想着能够把自己遇到的坑和注意事项记录下来,即方便自己后面查阅也可以分享给大家,这是一种冲动,但是这个或许并不是原厂希望的,尽管这样有可能会牺牲一些时间也有哪天原…

EasyNVR中HTTP-FLV协议无法播放怎么解决?

在科技日新月异的今天&#xff0c;摄像头作为公共安全领域的重要一环&#xff0c;其技术的不断提升正显著地改变着社会的安全格局。从最初的简单监控到如今的高清智能分析&#xff0c;我们可以对特定区域进行实时监控和记录&#xff0c;为社会的安全稳定提供了强有力的保障。 问…

linux环境GitLab服务部署安装及使用

一、GitLab介绍 GitLab是利用Ruby onRails一个开源的版本管理系统&#xff0c;实现一个自托管的Git项目仓库&#xff0c;可通过Web界面进行访问公开的或者私人项目。 二、GitLab安装 1、先安装相关依赖 yum -y install policycoreutils openssh-server openssh-clients postf…

视频码率到底是什么?详细说明

视频码率&#xff08;Video Bitrate&#xff09;是指在单位时间内&#xff08;通常是每秒&#xff09;传输或处理的视频数据量&#xff0c;用比特&#xff08;bit&#xff09;表示。它通常用来衡量视频文件的压缩程度和质量&#xff0c;码率越高&#xff0c;视频质量越好&#…

DICOM MPPS详细介绍

文章目录 前言一、常规检查业务流程二、MPPS的作用三、MPPS的原理1、MPPS与MWL2、MPPS服务过程 四、MPPS的实现步骤1、创建实例2、传递状态 五、总结 前言 医院中现有的DICOM MWL(Modality Worklist)已开始逐渐得到应用&#xff0c;借助它可以实现病人信息的自动录入&#xff0…

如何手搓一个智能激光逗猫棒

背景 最近家里的猫胖了&#xff0c;所以我就想做个逗猫棒。找了一圈市场上的智能逗猫棒&#xff0c;运行轨迹比较单一&#xff0c;互动性不足。 轨迹单一&#xff0c;活动范围有限 而我希望后续可以结合人工智能物联网&#xff0c;通过摄像头来捕捉猫的位置&#xff0c;让小…