封装map和set

文章目录

  • 封装
  • map
  • set
  • 红黑树
    • 成员变量
    • 节点定义
    • KeyOfT
    • MapKeyOfT
    • SetKeyOfT
  • begin() && end()
  • 迭代器
    • 迭代器类
    • operator++
    • operator- -
  • insert

封装

map和set的底层都是通过红黑树来实现的,那么是怎么做到共用同一份代码,但让map存储的是键值对,set只存储键值的呢?

map

template<class K, class V>
class map
{
private:
	RBTree<K, pair<const K, V>, MapKeyOfT> _t;
};
}

set

template<class K> 
class set
{
private:
		RBTree<K, K, SetKeyOfT> _t;	
	};
}

上面的map和set的成员变量类型分别是RBTree<K, pair<const K, V>, MapKeyOfT>和RBTree<K, K, MapKeyOfT>,它在实例化时会调用底层的红黑树,对于map而言它调用的红黑树会将节点存储的value实例化成键值对pair<const K, V>,而对于set它会将节点实例化成键值。

template <class K, class T, class KeyOfT>
struct RBTree
{
typedef RBTreeNode<T> Node;
};
enum Colour
{
	RED,
	BLACK
};

template<class T>
struct RBTreeNode
{
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;
	//pair<K, V> _kv;
	T _data;
	Colour _col;

	RBTreeNode(const T& data)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _data(data)
		, _col(RED)//新节点默认为红色
	{

	}
};

红黑树

成员变量

// set->RBTree<K, K, SetKeyOfT> _t;
//map->RBTree<K, pair<K, V>, MapKeyOfT> _t
template <class K, class T, class KeyOfT>
struct RBTree
{
	//typedef RBTreeNode<K, V> Node;
	typedef RBTreeNode<T> Node;
public:
	//同一个类模板,传不同的实参实例化出的不同类型
	typedef __TreeIterator<T, T*, T&> iterator;
	typedef __TreeIterator<T, const T*, const T&> const_iterator;
	private:
	Node* _root = nullptr;
public:
	size_t _rotateCount = 0;//旋转次数
};

通过上面我们会发现,对于set而言T会实例化成key,而对于map它的T会实例化成pair<K, V>,那么我们就又会有个疑问,这不都已经把key都给传过来了吗?为什么还要一个单独的模板参数K,它是为了解决例如为了满足find函数,因为我们在找一个值的时候,是按Key去找的,因此给一个K模板参数会使得find更方便还有erase函数等

节点定义

这个节点可能存的是关键字K,也可能是键值对<K,V>结构
节点成员变量存储的数据要怎么设计呢?是存储K,还是<K,V>呢?
不怕,我们有模板参数,我们只需要传递给该节点一个类型就可以了,如果是set我们就传递K对应类型就可以了,如果是map我们就传给他一个pair<K, V>类型

enum Colour
{
	RED,
	BLACK
};
template<class T>
struct RBTreeNode
{
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;
	T _data;
	Colour _col;

	RBTreeNode(const T& data)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _data(data)
		, _col(RED)//新节点默认为红色
	{

	}
};

因此如果是map这里的_data实例化出的类型就是pair<K, V>,如果是set它的类型就是K
为什么新插入的节点默认设为红色?
因为如果将新插入的节点设为黑色的话,那么每一条路径黑色节点都与该路径黑色节点个数不同,就要调整所有路径,而设为红色,都有可能不需要调整的情况,所以综合考虑,新插入节点设为红色对我们来说更加有利。

KeyOfT

为什么要在第一层封装时,设计出仿函数mapKeyOfT和setKeyOfT?
因为底层实现是相同的,我们在插入一个新的元素时,都要用K去比较,然后找到合适的位置进行插入,对于set而言它的K就是节点中的_data,而对于map而言,_data类型是键值对,它的K是_data.first,如果是这样的话,我们在底层就不容易实现一份代码既可以对set的K比较,又要同时满足map的K比较。因此我们添加了一个仿函数,来获取map和set的K值。

MapKeyOfT

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

SetKeyOfT

struct SetKeyOfT
{
	const K& operator()(const K& key)
	{
		return key;
	}
};

因此在插入等操作需要进行比较时,我们直接调用仿函数来获取该节点对应的K值即可。
那么为什么不直接在仿函数中比较好,而是只返回对应的key值呢?因为我们不仅要满足K与K,键值对与键值对的比较,还要满足K与键值对.first的比较,当然你要想在这比较好也可以,只不过就要实现多份仿函数重载例如const K& operator()(const pair<K, V>& kv,const K& key),const K& operator()(const K& key, const pair<K, V>& kv)

Node* cur = _root;
Node* parent = nullptr;
KeyOfT kot;
//找到要插入的位置
while (cur)
{
	if (kot(cur->_data) < kot(data))
	{
		parent = cur;
		cur = cur->_right;
	}
	else if (kot(cur->_data) > kot(data))
	{
		parent = cur;
		cur = cur->_left;
	}
	else// if(kv.first == cur->_kv.first)
	{
		//assert(false);
		//return false;
		return make_pair(iterator(cur), false);
	}
}

begin() && end()

第一层:
map

public:
	typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::iterator iterator;
	typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::const_iterator const_iterator;

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

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

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

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

set

public:
	typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;
	typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;

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

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

set无论是普通对象还是const对应调用的都是const迭代器,因此我们只写一份const对象调用即可
红黑树对应的底层实现:
树的遍历是中序遍历,是有序的,因此begin的返回值是最左边的,在左小右大的情况下就是返回最小的节点指针,而end()返回值是nullptr

public:
	//同一个类模板,传不同的实参实例化出的不同类型
	typedef __TreeIterator<T, T*, T&> iterator;
	typedef __TreeIterator<T, const T*, const T&> const_iterator;

	//返回的是迭代器对象?
	iterator begin()
	{
		Node* leftNode = _root;
		while (leftNode && leftMin->_left)
		{
			leftNode = left->_left;
		}
		return iterator(leftNode);
	}

	iterator end()
	{
		return iterator(nullptr);
	}

	const_iterator begin() const
	{
		Node* leftNode= _root;
		while (leftNode&& leftNode->_left)
		{
			leftNode= leftNode->_left;
		}
		return const_iterator(leftNode);
	}

	const_iterator end() const
	{
		return iterator(nullptr);
	}

迭代器

我们先解决一个重要问题,map和set是怎么做到共用一份代码的情况下set不允许修改但map.second允许被修改的?在红黑树底层做的?不是,是在第一层封装时进行实现的。
map

template<class K, class V>
class map
{
private:
	RBTree<K, pair<const K, V>, MapKeyOfT> _t;
};

set

template<class K> 
class set
{
private:
	RBTree<K, K, SetKeyOfT> _t;
};

注意map中的pair<const K, V>它的K是const的,因此这就首先保证了无论是普通迭代器还是const迭代器,map的K是一定不会被修改的。那么set又是怎么做到它的K无论是普通迭代器还是const迭代器都不允许被修改的呢?
map

public:
typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::iterator iterator;
typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::const_iterator const_iterator;

set

public:
typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;
typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;

set在对迭代器进行typedef时,它是让普通迭代器和const迭代器在实例化时,都实例化成了const迭代器,因此就做到了K无论是普通迭代器还是const迭代器都不允许被修改,而map普通迭代器就是普通迭代器,const迭代器就是const迭代器。

迭代器类

template <class T, class Ptr, class Ref>
struct __TreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef __TreeIterator<T, Ptr, Ref> Self;
	typedef __TreeIterator<T, T*, T&> Iterator;

	__TreeIterator(const Iterator& it)//构造函数
		:_node(it._node)
	{

	}

	Node* _node;

	__TreeIterator(Node* node)//构造函数
		:_node(node)
	{}

operator++

++是中序遍历,找到下一个节点

Self& operator++()
{
	if (_node->_right)
	{
		// 右树的最左节点(最小节点)
		Node* subLeft = _node->_right;
		while (subLeft->_left)
		{
			subLeft = subLeft->_left;
		}

		_node = subLeft;
	}
	else
	{
		Node* cur = _node;
		Node* parent = cur->_parent;
		// 找孩子是父亲左的那个祖先节点,就是下一个要访问的节点
		while (parent && cur == parent->_right)
		{
			cur = cur->_parent;
			parent = parent->_parent;
		}

		_node = parent;
	}

	return *this;
}

就是一直向上去寻找直到找到当前cur节点是父亲的左孩子的那个节点,就是下一个要访问的节点

operator- -

–是倒着的中序遍历,也就是右根左

Self& operator--()
{
	if (_node->_left)
	{
		Node* subRight = _node->_left;
		while (subRight->_right)
		{
			subRight = subRight->_right;
		}

		_node = subRight;
	}
	else
	{
		// 孩子是父亲的右的那个节点
		Node* cur = _node;
		Node* parent = cur->_parent;
		while (parent && cur == parent->_left)
		{
			cur = cur->_parent;
			parent = parent->_parent;
		}

		_node = parent;
	}
	return *this;
}

就是一直向上去寻找直到找到当前cur节点是父亲的右孩子的那个节点,就是下一个要访问的节点

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

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

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

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

Ref就是引用,Ptr就是指针

insert

insert是上层的接口,用于去插入一个节点
map

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

set

pair<iterator, bool> insert(const K& key)
{
	pair<typename RBTree<K, K, SetKeyOfT>::iterator, bool> ret = _t.insert(key);
	return pair<iterator, bool>(ret.first, ret.second);
}

map就是直接将这个键值对插入底层红黑树所得到的返回值返回即可,而对于set而言插入并没有这么简单,他不能直接返回红黑树的返回值,他还要借助一个中间变量将红黑树的返回值做一些修改才可以返回,这是为什么呢?
set中直接返回底层Insert的返回值出现的问题如下

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

在这里插入图片描述
出现该错误的原因是类型不匹配,注意这是个pair类型,不是指针或引用,不能由普通对象隐式转化为const对象,不匹配就是不匹配,那么为什么会出现这种问题,而对于map而言就可以直接把红黑树中Insert的返回值作为insert的返回值?
这是因为在set中无论是普通迭代器还是const在底层调用的都是const迭代器,这里_t是普通对象,当它调用底层Insert函数时,其实返回的pair对象first是普通迭代器,但是在set中返回值pair<iterator, bool> 的first是const迭代器,因此就会出现上面的问题,pair对象是一个整体,不能把first单拿出来说它是指针,可以由普通转换为const,pair对象就可以。
因此现在的问题也就是底层Insert返回的pair对象是普通的对象,我们要把这个返回值转换为const的然后去返回到上层,但是不能直接返回因为pair对象不支持隐式类型转换为const对象
如何解决?

pair<iterator, bool> insert(const K& key)
{
	pair<typename RBTree<K, K, SetKeyOfT>::iterator, bool> ret = _t.Insert(key);
	return pair<iterator, bool>(ret.first, ret.second);		
}

我们把Insert的返回先保存起来,注意这里pair对象的first的类型为
typename RBTree<K, K, SetKeyOfT>::iterator,是底层红黑树对应的迭代器类型,也就是此时ret的first类型是普通的迭代器,保证了pair对象ret与Insert返回值对象的类型匹配。
在这里插入图片描述
接下来又会出现这种问题,为什么?因为此时没有匹配的构造函数

typedef __TreeIterator<T, T*, T&> Iterator;

__TreeIterator(const Iterator& it)//构造函数
	:_node(it._node)
{
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

多功能演示工具ProVideoPlayer2 mac特色介绍

ProVideoPlayer2 mac是用于大多数任何生产的首选多功能演示工具。ProVideoPlayer 2是一种动态视频播放和处理媒体服务器&#xff0c;可将视频映射&#xff08;包括播放和实时视频输入&#xff09;实时控制到一个或多个输出。包括实时效果&#xff0c;调度&#xff0c;网络同步和…

(AntV X6)vue2项目流程图实现

(AntV X6)vue2流程图实现 项目&#xff1a;gitLab/zhengzhouyuan 效果&#xff1a; 一、项目引入X6 npm install antv/x6 --save 二、引入相关插件 npm install --save antv/x6-plugin-clipboard antv/x6-plugin-history antv/x6-plugin-keyboard antv/x6-plugin-selection an…

海云安亮相2023北京国际金融安全论坛,助力金融企业数字化转型降本增效

近日&#xff0c;2023北京国际金融安全论坛暨金融科技标准认证生态大会在北京金融安全产业园成功举办。深圳海云安网络安全技术有限公司&#xff08;以下简称“海云安”&#xff09;受邀参展亮相此次大会。海云安作为国内领先的金融科技服务商&#xff0c;展示了开发安全系列产…

数据结构:图文详解 树与二叉树(树与二叉树的概念和性质,存储,遍历)

目录 一.树的概念 二.树中重要的概念 三.二叉树的概念 满二叉树 完全二叉树 四.二叉树的性质 五.二叉树的存储 六.二叉树的遍历 前序遍历 中序遍历 后序遍历 一.树的概念 树是一种非线性数据结构&#xff0c;它由节点和边组成。树的每个节点可以有零个或多个子节点…

输入两个时间,判断时间是否为非工作日,并且是日期否为同一天。是的话返回true,否返回false

工作遇到这么一个逻辑&#xff0c;前端回传两个时间&#xff08;必须是两个那一种&#xff09;。然后&#xff0c;我后端需要判断这两个时间是否为同一天&#xff0c;并且这个时间是否为非工作日&#xff0c;是的话返回true&#xff0c;反之返回false 代码&#xff1a; packa…

2023/12/26 work

1. 定义自己的命名空间myspace&#xff0c;并在myspace中定义一个字符串&#xff0c;实现求字符串大小的函数。 #include <iostream>using namespace std;namespace mynamespace {string name;unsigned int strlen(string name){return name.size();} }using namespace…

面试题之二HTTP和RPC的区别?

面试题之二 HTTP和RPC的区别&#xff1f; Ask范围&#xff1a;分布式和微服务 难度指数&#xff1a;4星 考察频率&#xff1a;70-80% 开发年限&#xff1a;3年左右 从三个方面来回答该问题&#xff1a; 一.功能特性 1)HTTP是属于应用层的协议&#xff1a;超文本传输协议…

手机蓝牙在物联网超市中的应用

超市一站式购物已进入城市的千家万户。然而人们在选购时却采用直接翻阅商品的方式&#xff0c;既不方便又不卫生甚至大大缩短食品类商品保质期&#xff0c;也给超市商品管理造成很大难度。物联网(The Internet of things)基于射频识别(RFID)、红外感应等技术&#xff0c;把物品…

【PostGIS】在Java中操作postgis——使用springboot+Maven+mybatis框架

前言&#xff1a; PostgreSQL15对应PostGIS安装教程及空间数据可视化 空间数据库-常用空间函数 完成PostGIS的安装与配置后&#xff0c;让我们来写一个Java操作postgis数据库的demo吧~ 使用工具&#xff1a; NavicatIDEA 一、PostGIS数据库准备 在Navicat中新建一个postgr…

云渲染UE4像素流送搭建(winows、ubuntu单实例与多实例像素流送)

windows/ubuntu20.4下UE4.27.2像素流送 像素流送技术可以将服务器端打包的虚幻引擎应用程序在客户端的浏览器上运行&#xff0c;用户可以通过浏览器操作虚幻引擎应用程序&#xff0c;客户端无需下载虚幻引擎&#xff0c;本文实现两台机器通过物理介质网线实现虚幻引擎应用程序…

Golang 协程配合管道

请完成goroutine和channel协同工作的案例&#xff0c;具体要求&#xff1a; &#xff08;1&#xff09;开启一个writeData协程&#xff0c;向管道mtChan中写入50个整数. &#xff08;2&#xff09;开启一个readData协程&#xff0c;从管道intChan中读取writeData写入的数据。 &…

系列十(实战)、发送 接收批量消息(Java操作RocketMQ)

一、发送 & 接收批量消息 1.1、概述 批量消息是指RocketMQ可以把一组消息集合一次性发送&#xff0c;这一组消息会被当做一个消息供消费者消费。 1.2、Demo05MQTestApp /*** Author : 一叶浮萍归大海* Date: 2023/12/25 11:48* Description: 发送 & 接收批量消息*/ …

智能优化算法应用:基于袋獾算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于袋獾算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于袋獾算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.袋獾算法4.实验参数设定5.算法结果6.参考文献7.MA…

python CodeFormer 图像(人脸面部)修复源码

介绍 github地址&#xff1a;https://github.com/sczhou/CodeFormer [NeurIPS 2022] Towards Robust Blind Face Restoration with Codebook Lookup Transformer 效果&#xff1a; 测试环境&#xff1a; anconda3python3.8 torch1.9.0cu111 pyqt5 部分代码&#xff1a; i…

记一次应急响应练习(windows)

记一次应急响应练习&#xff08;windows&#xff09; windows&#xff1a; 1.请提交攻击者攻击成功的第一时间&#xff0c;格式&#xff1a;YY:MM:DD hh:mm:ss 答&#xff1a;2023/04/29:22:44:32 思路&#xff1a; 看见桌面的小皮面板&#xff0c;进入小皮的安装目录。发现…

nodejs进阶

文章目录 写在前面一、dependencies、devDependencies和peerDependencies区别&#xff1a;二、需要牢记的npm命令2.1 npm init2.2 npm config list2.3 npm配置镜像源 三、npm install 的原理四、package-lock.json的作用五、npm run 的原理六、npx6.1 npx是什么6.2 npx的优势6.…

一个卖美妆的 一个月招了数十万代理!月销售额破亿 你敢相信吗?

商业模式永不过时 大家好&#xff0c;我是吴军&#xff0c;一家软件公司的产品经理 今天我们来聊一下这个纪炫商城 其实&#xff0c;说这个纪炫商城之前&#xff0c;我想跟各位企业家老板聊几句实在话 作为公司两百多号技术的&#xff0c;一个拥有五年软件开发经验的产品经理…

深入探讨Java反射:解析机制与应用场景

当谈及Java编程语言的强大功能时&#xff0c;反射&#xff08;Reflection&#xff09;是一个不可忽视的特性。反射允许程序在运行时检查和操作其自身的结构&#xff0c;这为开发者提供了一种动态获取信息和执行操作的途径。在本篇博客中&#xff0c;我们将深入探讨Java反射的原…

分支限界法求解01背包(优先队列)【java】

实验内容&#xff1a;运用分支限界法解决0-1背包问题 实验目的&#xff1a;分支限界法按广度优先策略遍历问题的解空间树&#xff0c;在遍历过程中,对已经处理的每一个结点根据限界函数估算目标函数的可能取值&#xff0c;从中选取使目标函数取得极值的结点优先进行广度忧先搜…