【map和set的封装】

news2025/1/11 21:00:53

文章目录

  • 前言
  • 1 大致框架
  • 2 迭代器
  • 3 map和set的封装


前言

上篇博客已经讲解了红黑树插入的模拟实现,这篇文章的目的是利用上节课讲解的底层实现来封装map和set.参考代码借鉴的是STL SGI版本3.0


1 大致框架

首先我们来看看源码里面怎么定义的:

在这里插入图片描述

在这里插入图片描述从源码中我们不难发现map和set底层是用了一颗红黑树来封装的,并且模板参数与我们自己想的不太一样。set中传入的是<K,K>,map中传入的是<K,Pair<K,V>> (其他参数可以暂时不用考虑)
大家想想,为什么要这么设计❓这样设计的好处是什么❓
我们传入两个参数的目的就是为了用一颗红黑树封装map和set,也就是第二个参数我们可以理解为给的是一个T,T可以接受上层的传入来的参数。

那可能大家又有了疑问?那为啥要传入第一个参数呀?直接用第二个参数不行吗?
大家别忘了,我们使用find接口和erase接口是用的参数是啥?是不是无论是map还是set都是用的是K,所以这个参数我们必须的传。

但是这样做问题又来了,上层是如何知道我们比较结点大小的时候比较的是K,还是Pair<K,V>?
所以我们还得再传入一个模板参数,不妨给一个仿函数,通过仿函数来取得数据。


2 迭代器

同样,迭代器往往就是容器中最精华的部分,所以迭代器的设计也是有着举足轻重的地位,这里迭代器的设计思路类似于链表的迭代器,不过具体实现却是比链表更加复杂,接下来我们便来看看。

* -> == !=这些运算符重载好说,实现起来不难,关键是如何实现++重载?–重载?
给了一颗红黑树,如下图,我们如何走到下个结点呢?
在这里插入图片描述我们不妨采用这种思路:如果右子树存在,就找右子树的最左结点;右子树不存在,需要判断是否孩子是父亲的右节点,是的话还要往祖宗向上找,直到找到孩子是父亲的左节点为止。

我们不妨来举一个例子:假如当前在1这个结点,由于1的右子树存在,且6这个结点恰好是1右子树的最左节点,所以++后应该走到了6;假如现在当前结点为11,由于11的右子树为空,所以要往上找,直到找到孩子是父亲左节点为止:11往上找父亲为8,8的右孩子是11,所以没有找到孩子是父亲右孩子的情况继续往上走,孩子为8,父亲为13,13的左孩子为8,随意此时找到了孩子是父亲左的那一个,所以++后就走到了13这个位置。

同理- -运算符的重载可以与++运算符重载反着来,思路类似:如果左子树存在,就找左子树的最右结点;左子树不存在,需要判断是否孩子是父亲的右节点,是的话还要往祖宗向上找,直到找到孩子是父亲的右节点为止。
这个我就不再分析了,大家可以自行分析。

为了方便,我们将用nullptr来构造找到了末尾,不用继续找了,但是STL中并不是这样设计的,是用了一个头结点来连接:
在这里插入图片描述走到了头结点就表示找到了末尾。
用这种方式会稍微麻烦些,不过总体也是不难的。

然后我们自己便可以实现一份迭代器了:

template<class T, class Ref, class Ptr >
struct __RBTreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef __RBTreeIterator<T, Ref, Ptr> Self;
	
	Node* _node;
	__RBTreeIterator(Node* node)
		:_node(node)
	{}


	Ref operator*()
	{
		return _node->_data;
	}

	Ptr operator->()
	{
		return &_node->_data;
	}

	bool operator==(const Self& self)const
	{
		return _node == self._node;
	}

	bool operator!=(const Self& self)const
	{
		return _node != self._node;
	}

	Self& operator++()
	{
		if (_node->_right)//右子树存在,就找右子树的最左结点
		{
			Node* left = _node->_right;
			while (left->_left)
			{
				left = left->_left;
			}
			_node = left;
		}
		else
		{
			//右子树不存在,需要判断是否孩子是父亲的右节点,是的话还要往祖宗向上找,直到找到
			//孩子是父亲的左节点为止
			Node* child = _node;
			Node* parent = _node->_parent;
			while (parent && parent->_right == child)
			{
				child = parent;
				parent = parent->_parent;
			}
			//走到这里说明找到了孩子是父亲的左节点或者
			//孩子已经是根节点了(表明在根节点也没有找到,说明已经遍历完成了)
			_node = parent;
		}
		return *this;
	}

	Self& operator--()
	{
		if (_node->_right)//左子树存在,就找左子树的最右结点
		{
			Node* right = _node->_left;
			while (left->_right)
			{
				left = left->_right;
			}
			_node = right;
		}
		else
		{
			//左子树不存在,需要判断是否孩子是父亲的左节点,是的话还要往祖宗向上找,直到找到
			//孩子是父亲的右节点为止
			Node* child = _node;
			Node* parent = _node->_parent;
			while (parent && parent->_left == child)
			{
				child = parent;
				parent = parent->_parent;
			}
			//走到这里说明找到了孩子是父亲的右节点或者
			//孩子已经是根节点了(表明在根节点也没有找到,说明已经遍历完成了)
			_node = parent;
		}
		return *this;
	}
};


3 map和set的封装

其他的都好说,关键是如何实现set不能够修改,而map中可以修改Val;
我们可以采取这种方式:set的普通迭代器我们用上层的const迭代器实现,set的const迭代器我们也用上层的const迭代器实现。map的话我们只需要将==第二个模板参数给pair<const K,V>==就可以了。

set.hpp:

namespace grm
{
	template<class K>
	class set
	{
		struct SetOfKey
		{
			const K& operator()(const K& k)
			{
				return k;
			}
		};
	private:
		RBTree<K, K, SetOfKey> _rbTree;
		//typedef typename RBTree<K, const K, SetOfKey>::Iterator iterator;
		//不能够像上面这样传入参数
		typedef typename RBTree<K, K, SetOfKey>::ConstIterator iterator;
		typedef typename RBTree<K, K, SetOfKey>::ConstIterator const_terator;

	public:

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

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

		const_terator begin()const
		{
			return _rbTree.begin();
		}

		const_terator end()const
		{
			return _rbTree.end();
		}

		pair<iterator, bool> insert(const K& k)
		{
			return _rbTree.insert(k);
		}
	};

map.hpp:

namespace grm
{
	template<class K, class V>
	class map
	{
		struct MapOfKey
		{
			const K& operator()(const pair<const K, V>& kv)
			{
				return kv.first;
			}
		};
	private:
		RBTree<K, pair<const K,V>, MapOfKey> _rbTree;
	public:
		typedef typename RBTree<K, pair<const K, V>, MapOfKey>::Iterator iterator;
		typedef typename RBTree<K, pair<const K, V>, MapOfKey>::ConstIterator const_iterator;

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

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

		const_iterator begin()const
		{
			return _rbTree.begin();
		}

		const_iterator end()const
		{
			return _rbTree.end();
		}

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

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

但是这样实现set时也还是会遇到问题:那就是我们用了普通迭代器来构造const迭代器,这样势必是会报错的,有什么处理方式吗?
我们可以在迭代器中多给出一个构造:

//模板参数是普通迭代器就是拷贝构造
	//模板参数是const迭代器就是用普通迭代器构造const迭代器
	__RBTreeIterator(const __RBTreeIterator<T,T&,T*>& it)
		:_node(it._node)
	{}

这样就能够解决问题了。

如果大家还有哪里不懂的地方可以私信博主,有需要的可以参考博主的码云:
【码云地址】


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

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

相关文章

什么是分布式事务?

什么是分布式事务&#xff1f; 分布式对应的是单体架构&#xff0c;互联网早起单体架构是非常流行的&#xff0c;好像是一个家族企业&#xff0c;大家在一个家里劳作&#xff0c;单体架构如下图&#xff1a; 但是随着业务的复杂度提高&#xff0c;大家族人手不够&#xff0c;…

Redis中的哈希结构(Dict)

前言 哈希结构是一个在计算机中非常常见的结构。哈希结构可以让我们在O(1)时间复杂度查找元素并且对其操作&#xff0c;并且增删改查性能并不会随着数据量的增多而改变。反而数据量的增大&#xff0c;会出现两个关键问题&#xff0c;一个是哈希冲突&#xff0c;另一个是rehash…

15、Spring框架

目录 什么是Spring Spring优点 Spring体系结构 Spring新特性 Spring的入门程序 新建立Maven项目 创建名为HelloSpring的类 新建applicationContext.xml文件 XML文件的约束信息配置 测试类TestHelloSpring 控制反转 依赖注入 依赖注入和控制反转的比较 依赖注入的…

虚拟机类加载机制

目录 1、概述 2、类加载的过程 1、过程总览 2、加载 3、链接-验证 4、链接-准备 5、链接-解析 6、初始化 7、总结 3、类加载的时机 4、类加载器 1、概述 2、类与类加载器 3、三层类加载器 4、双亲委派模型 5、其他加载策略 1、概述 一个Java类会被编译成一个Cl…

grep,sed,awk实战

grep -E ^(root|sshd)\> /etc/passwd 找出以root或者sshd开头的&#xff0c;且只含root或者sshd,>表示匹配到root或者sshd就结束了&#xff0c;类似情况如下图&#xff1a; grep -c ^yu /etc/passwd 匹配含yu这个用户出现的次数 grep -m 2 ^yu /etc/passwd …

arthas使用

文章目录 ArthasArthas&#xff08;阿尔萨斯&#xff09;能为你做什么&#xff1f;安装1.linux中使用2.docker中使用 命令列表jvm 相关class/classloader 相关monitor/watch/traceprofiler/火焰图 Arthas Arthas 是一款线上监控诊断产品&#xff0c;通过全局视角实时查看应用 …

windows nvm 安装过程

1. 官网 Releases coreybutler/nvm-windows GitHubA node.js version management utility for Windows. Ironically written in Go. - Releases coreybutler/nvm-windowshttps://github.com/coreybutler/nvm-windows/releases 下载 nvm-setup.exe; 2. 安装完成后后&#x…

Golang Channel 实现原理与源码分析

Do not communicate by sharing memory; instead, share memory by communicating. 通过通信来共享内存&#xff0c;而不是共享内存来通信 安全访问共享变量是并发编程的一个难点&#xff0c;在 Golang 语言中&#xff0c;倡导通过通信共享内存&#xff0c;实际上就是使用 chan…

23种设计模式之访问者模式(Visitor Pattern)

前言&#xff1a;大家好&#xff0c;我是小威&#xff0c;24届毕业生&#xff0c;在一家满意的公司实习。本篇文章将23种设计模式中的访问者模式&#xff0c;此篇文章为一天学习一个设计模式系列文章&#xff0c;后面会分享其他模式知识。 如果文章有什么需要改进的地方还请大佬…

chatgpt没有免费版的吗?如何使用ChatGPT?

ChatGPT是基于GPT模型的聊天机器人&#xff0c;目前没有免费版。ChatGPT是由OpenAI开发的&#xff0c;OpenAI的GPT模型需要大量的计算资源和技术支持&#xff0c;因此需要付费才能使用。 目前&#xff0c;OpenAI提供了两种方式来使用GPT模型&#xff1a; 1. OpenAI API OpenA…

制造型企业降本增效的最佳工具,质量管理系统,该如何利用好

许多制造业企业质量管理主要用于解决制造业质检效率低下、作业不规范等难题&#xff0c;形成质量检验、质量方案、档案数据、统计分析一体化的质量管理体系&#xff0c;有效为企业质量管理提速降本增效&#xff0c;实现企业数字化转型。在没有正确利用质量管理系统之前&#xf…

45个 Cha​tGPT 常用插件说明

45个 ChatGPT 常用插件说明 ChatGPT常用的45个插件&#xff0c;以及它们用途说明&#xff1a; 1/ Slack&#xff1a;查询Slack信息 2/ Zapier&#xff1a;与5000应用&#xff0c;如Google Sheets和Docs进行交互。 3/ Expedia&#xff1a;在一个地方激活你的旅行计划 4/ Kla…

Worldclim(v1.4、v2.1)数据集使用介绍

最近在使用Worldclim的数据&#xff0c;在这里记录一下该数据集的使用。 如果你想得到过去、现在和未来的气候数据&#xff0c;那么你可以使用这个数据集&#xff1a;Worldclim数据集 该数据集包含了4种时期的气候数据&#xff1a;历史时期的末次盛冰期、全新世中期、当前时…

操作系统(3.3)--线程的实现方式

进程调度的任务、机制和方式 1.进程的调度任务 进程调度的任务主要有三&#xff1a; (1)保存处理机的现场信息。在进程调度进行调度时&#xff0c;首先需要保存当前进程的处理机的现场信息&#xff0c;如程序计数器、多个通用寄存器中的内容等 (2)按某种算法选取进程。调度…

脉冲神经网络深度残差学习(ResNet)

来源&#xff1a;投稿 作者&#xff1a;小灰灰 编辑&#xff1a;学姐 论文标题&#xff1a;Deep Residual Learning in Spiking Neural Networks 论文链接: https://arxiv.org/pdf/2102.04159v3.pdf 代码链接&#xff1a;https: //github.com/fangwei123456/Spike-Element-Wi…

MYSQL数据库基础(数据库)

文章目录 一、数据库使用流程二、数据库的操作三、常用数据类型3.1 数值类型3.2 字符串类型3.3 日期类型 四、数据表操作 一、数据库使用流程 用户在客户端输入SQL语句客户端会把SQL通过网络发送给服务器服务器会执行这个SQL&#xff0c;把结果返回给客户端客户端接收到结果后…

第十九篇、基于Arduino uno,获取光电开关(NPN/PNP型)的信号——结果导向

0、结果 说明&#xff1a;先来看看串口调试助手显示的结果&#xff0c;如果有遮挡会输出低电平或者高电平&#xff0c;没有遮挡会输出高电平或者低电平&#xff0c;如果是你想要的&#xff0c;可以接着往下看。 1、外观 说明&#xff1a;这里要区分到底是NPN型号的&#xff0…

分享几个索引创建的小 Tips

文章目录 1. 冗余索引1.1 联合索引左边列1.2 索引中加入主键 2. 隐藏的索引排序3. 删除不使用的索引4. 手动更新索引统计信息5. 适时优化表 关于 MySQL 中的索引&#xff0c;松哥前面已经和小伙伴们聊了不少了&#xff0c;不过在索引使用的时候&#xff0c;还是有一些需要注意的…

如何发布一个npm包

1、注册账号 https://www.npmjs.com/ 使用邮箱注册即可 a. 邮箱会在本地登录时发送验证码使用 b. 发布包后邮箱会收到通知 2、生成AccessToken &#xff08;1&#xff09;直接本地登录 # 根据提示输入用户名、密码、注册邮箱 npm login# 输入完邮箱会发送验证码&#xff0c…

如何做一个有质量的技术分享

分享信息并不难,大多数人都能做到,就算是不善言谈性格内向的技术人员,通过博客或社交媒体,或是不正式的交流,他们都能或多或少的做到。但是如果你想要做一个有质量有高度的分享,这个就难了。 所谓的有质量和有高度,我心里面的定义有两点: 分享内容的保鲜期是很长的会被…