【map和set的封装】

2023-11-06


前言

上篇博客已经讲解了红黑树插入的模拟实现,这篇文章的目的是利用上节课讲解的底层实现来封装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)
	{}

这样就能够解决问题了。

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


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【map和set的封装】 的相关文章

随机推荐

  • cmd /c /k

    cmd c dir 是执行完dir命令后关闭命令窗口 cmd k dir 是执行完dir命令后不关闭命令窗口 cmd c start dir 会打开一个新窗口后执行dir指令 原窗口会关闭 cmd k start dir 会打开一个新窗口后
  • 【串口屏】迪文的串口屏下载程序SD卡如何格式化

    用SD卡下载程序到迪文的串口屏需要先将SD卡格式化 而直接鼠标右键快速格式化这种方式不行 必须通过字符命令行格式化才行 即运行 cmd命令 具体如下 SD卡需要格式化成屏能识别的格式 开始 运行 输入cmd进入DOS系统 输入format
  • (二)vue.js的设计模式

    概述 Vue js在设计上采用mvvm模式进行设计 mvvm模式刚出来时一度的被人奉为神一般的模式 好像不懂mvvm模式就脱离了时代一样 什么是mvvm模式呢 Mvvm的全称为 Model View ViewModel m表示数据模型层 v
  • JavaScript 导出CSV文件

    列标题 逗号隔开 每一个逗号就是隔开一个单元格 var str 项目名称 项目信息 n for let i 0 i lt checkedItems length i var data checkedItems i str data Item
  • mysqlbackup企业版物理备份工具使用

    MySQL Enterprise Backup 简称MEB MEB8 对应的是MySQL8 0版本 8 0 28 备份工具的版本和MySQL版本要一致 MEB8 新特性 参考链接 https dev mysql com doc mysql
  • Java设计模式——建造者模式

    文章目录 为什么要有建造者模式 建造者模式 为什么要有建造者模式 工厂类模式提供的是创建单个类的模式 而建造者模式则是将各种产品集中起来进行管理 用来创建复合对象 所谓复合对象就是指某个类具有不同的属性 建造者模式与工厂模式的区别 工厂模式
  • 究竟什么是in-place?它在autograd当中怎么注意? 含grad函数的使用的一个小点

    1 a 0 b 0 是in place 2 a a 1 不是in place 3 a a permute 0 3 1 2 不是in place a view of a leaf Variable that requires grad is
  • 《国富论》笔记——货币

    上一个读书笔记 我简单发散思维到了货币 从以物易物到以贝壳作为货币举了一段例子 国富论 第四章就马上讲到了货币 并且补充了我很多未分析到的地方 货币的起源 当以物易物的时候 也许是因为我不再需要你的物品了 所以不再需要和你交换 你的东西只有
  • java实现的坦克大战(整理代码)

    本科做的小项目 现在整理一下 package T1 import java awt import javax swing import java awt event import java io import java util Vecto
  • 开学季征文

    目录 前言 一 自我介绍 二 前情回顾 2 1 摆烂大学生 2 2 从床上爬起来 三 新学期目标 1 刷算法 1 1 我与算法的缘 1 2 开始写题 1 3 为什么刷算法以及flag 1 4 计划参加一下蓝桥杯试试 2 学习英语和日语 2
  • 【区块链与密码学】第4-6讲:如何运用区块链实现防伪?

    本课堂用通俗易懂的系列内容为大家呈现区块链与密码学领域相关知识 这里有知识也有故事 从感兴趣到有乐趣 点宽课堂等你来学 课程首先从比特币着手进行入门介绍 再延伸至区块链的相关技术原理与发展趋势 然后深入浅出地依次介绍在区块链中应用的各类密码
  • vue 使用wangEditor

    1 npm install wangeditor core wangeditor editor wangeditor editor for vue 2 封装组件 MyEditor vue 这里是通过props content 将展示内容传入
  • 深度学习——Pay Attention to MLPs

    文章目录 前言 Gmlp 输入与输出 结构 个人理解 前言 前不久入职某大厂了 有点怀念无忧无虑的学生时代 入职后很快收到了第一个优化任务 算法岗的高不确定性确实会让人有一丝焦虑 目前体感来看 现有的深度学习模型性能非常依赖于数据质量 在数
  • matlab:作为TCP服务器,读取数据 V3版本

    和之前相比 增加数据处理和显示功能 matlab的速度 比起调试助手 慢了一个数量级 Labview 也不行 看来关键时刻还是C 靠谱 程序说明 读取采集的卡 转化 显示 按照下面的IP地址建立一个服务端 0 0 0 0地址可以监听所有的请
  • 【校招VIP】ChatGPT会取代程序员?5个成长阶段分析,如果真有那一天,可能人都是数字人了

    最近有同学问程序员会不会被chatGPT取代 包括很多短视频都在说怎么取代 但是请注意 我们正规的程序员开发工作是一个极其个性化的工作 换而言之 由于每个公司的具体要求是不一样的 因此程序员是很难被取代的 换句话说 如果某一天真的不需要程序
  • python之微信公众号文章获取(亲测可用)

    以十点读书为例 使用第三方抓包工具可以发现 这些链接就是历史文章请求url 源码如下 coding utf 8 import requests import json import re import pprint import time
  • 系分 - 结构化方法【概念】

    个人总结 仅供参考 欢迎加好友一起讨论 文章目录 系分 概念 结构化方法 结构化分析 SA Structured Analysis 结构化设计 SD Structured Design 结构化程序设计 SP Structured Progr
  • 机器学习入门-一元线性回归模型的骚操作

    文章适合于所有的相关人士进行学习 各位看官看完了之后不要立刻转身呀 期待三连关注小小博主加收藏 小小博主回关快 会给你意想不到的惊喜呀 文章目录 前言 一元线性回归模型讲解 我们可能会遭遇的问题 线性回归模型 数学公式推导 公式推导 代码介
  • An error occurred using the connection to database ‘‘ on server ‘.‘.

    如果是Mysql server要用127 0 0 1 不然会报两个错 1 An error occurred using the connection to database on server 2 An exception has bee
  • 【map和set的封装】

    文章目录 前言 1 大致框架 2 迭代器 3 map和set的封装 前言 上篇博客已经讲解了红黑树插入的模拟实现 这篇文章的目的是利用上节课讲解的底层实现来封装map和set 参考代码借鉴的是STL SGI版本3 0 1 大致框架 首先我们