map与set

set使用

 set在我们就是我们前面学习的k模型,它可以用来比对数据,增删查的时间复杂度都是O(logn)效率非常高,由于它底层的原因,它也可以实现排序,通过中序遍历可以输出我们的有序的数据;又因为它的T值在插入的时候具有唯一性的原因,set还有去重的作用;

set的接口

参数:

set的构造函数

set有无参的,迭代器区间的,拷贝构造函数;

set的迭代器

 set的迭代器都是双端迭代器

接下来,我们使用一些代码来试验一下set的这些接口:

int main()
{
    int arr[] = { 8,6,2,4,5,3,1,7,9 };
    set<int> s_range(arr, arr + 9);
    cout << "s_range: ";
    for (auto x : s_range)
    {
        cout << x << " ";
    }
    cout << endl;
    set<int> s_empty;
    s_empty.insert(5);
    s_empty.insert(8);
    s_empty.insert(4);
    s_empty.insert(9);
    s_empty.insert(7);
    s_empty.insert(3);
    set<int>::iterator ret = s_empty.begin();
    cout <<"s_empty: ";
    while (ret != s_empty.end())
    {
        cout << *ret << " ";
        ret++;
    }
    cout << endl;
    set<int> s_copy(s_empty);
    cout << "s_copy: ";
    for (auto x : s_copy)
    {
        cout << x << " ";
    }
    cout << endl;
}

可以看到我们可以使用三种不同的方式构造 ;

set的增删查接口

下面是set常用的接口,需要了解这些,可以到set - C++ Reference (cplusplus.com)网站上面清楚明了的查看;

我感觉set,map这些接口了解大致使用就好了,所以我不打算过多的描述;

set中还有一个multiset

multiset是允许键值冗余的,也就是说,当有相同元素插入的时候,是可以正常插入的multiset中的;其中我们如果我们使用find接口寻找这个值的时候,寻找的是中序遍历的第一个值;

实验:键值冗余时find寻找的元素

int main()
{
    int arr[] = { 5,3,7,2,4,6,6,6,8 };
    multiset<int> s;
    for (auto x : arr)
    {
        s.insert(x);
    }
    cout << "全部元素:";
    for (auto x : s)
    {
        cout  << x << " ";
    }
    cout << endl;
    auto ret=s.find(4);
 

我们可以看到如果是从中序的第一个那么就会find我们全部的6,如果非中序,那么输出的也就不是全部的6了;所以证明我们find是中序遍历的;

map使用

map存放了两个数据 ,它就是前面学的kv模型;一般我们使用map获得的返回值都是pair类型的,因为需要返回k,v两个值,所以将他们封装在了我们的一个类pair中;

map操作:

int main()
{
    map<int, string>m;
    m.insert(make_pair(3, "wangwu"));
    m.insert(make_pair(4, "zhaoliu"));
    m.insert(make_pair(1, "zhangsan"));
    m.insert(make_pair(2,"lisi"));
    for (auto x : m)
    {
        cout << x.first << ":" << x.second << endl;
    }

    return 0;
}

这里不再对map的接口做讲解,只放上简单的接口以及链接;因为stl的使用都是差不多的;

下面就是map常用的接口

map常用的接口

详细信息查看:map::map - C++ Reference (cplusplus.com) 

[]运算符重载

map最需要了解的就是[]运算符重载;

我们可以通过第一个key值来修改第二个value,也可以通过[key]直接插入我们的key到map中;

我们可以将它的操作看成这样

 我们通俗的可以翻译成这样;

这是写在map中的重载
V& operator[](const K& key)
{
    pair ret=this->insert(make_pair(key, V()));
    return ret.first.second;
}

其实就是先通过调用insert函数插入key,再通过insert的返回值获得key对应的value的引用,从而可以直接修改我们的value;

操作:

int main()
{
    map<int, string>m;
    m.insert(make_pair(3, "wangwu"));
    m.insert(make_pair(4, "zhaoliu"));
    m.insert(make_pair(1, "zhangsan"));
    m.insert(make_pair(2,"lisi"));
    for (auto x : m)
    {
        cout << x.first << ":" << x.second << endl;
    }
    cout << "----------------------------"<<endl;
    m[5];//插入操作,我们没有修改任何数据,但是map中没有这个元素5所以会直接插入
    for (auto x : m)
    {
        cout << x.first << ":" << x.second << endl;
    }
    cout << "----------------------------"<<endl;
    m[5] = "chengqi";//修改value操作
    for (auto x : m)
    {
        cout << x.first << ":" << x.second << endl;
    }
    cout << "----------------------------" << endl;

    return 0;
}

上面就是对map和set使用的介绍

接下来我们实现一下我们的map和set;

map与set的实现

(我们这里只是实现了map与set的插入接口和迭代遍历接口)

map与set是两个具有排序作用的高效率容器,并且map与set的底层都是红黑树,由于红黑树在进行平衡树时检查严格程度放松一些使得,红黑树的平均效率更高(不使用AVL树的原因);

并且由于map和set底层都使用的是RBTree,但是map和set分别是KV模型和K模型,它们底层的数据是不同的,所以为了让红黑树的代码可以同时适配map与set,红黑树的实现是有细节的,这也是STL大佬们的厉害之处;

想知道红黑树的实现可以看我前面的博客:

红黑树实现及原理-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/m0_75260318/article/details/137633090?spm=1001.2014.3001.5501

 改造后的红黑树:

模拟实现stl容器/模拟实现map与set/RBTree.hpp · future/my_road - 码云 - 开源中国 (gitee.com)

话不多说我们首先将我们map与set的实现代码拿出来:

由于使用了红黑树作为底层所以其实map和set自己实现的代码量很少都只是调用RBTree的接口而已;

map

template <class K, class V>
class myMap
{
	struct mapKofT
	{
		const K& operator ()(const pair<const K, V>& t)
		{
			return t.first;
		}
	};
public:
	typedef pair<const K, V> T;
	typedef typename RBTree<const K, T, mapKofT>::iterator iterator;
	typedef typename RBTree<const K, T, mapKofT>::const_iterator const_iterator;

	pair<iterator, bool>insert(const T& data)
	{
		return _tree.insert(data);

	}

	void print()
	{
		_tree.print();
	}

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

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

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

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

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


private:
	RBTree<const K, T, mapKofT> _tree;
};

 set

template <class K>
class mySet
{
	struct setKofT
	{
		const K& operator ()(const K& t)
		{
			return t;
		}
	};
public:
	typedef typename RBTree<K, K, setKofT>::const_iterator iterator;
	typedef typename RBTree<K, K, setKofT>::const_iterator const_iterator;

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

	void print()
	{
		_tree.print();
	}

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

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

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

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

private:
	RBTree<K, K, setKofT> _tree;
};

讲解

下面我们先来说说map和set它们做了什么,我们可以看到set和map的成员变量都是_tree但tree传的的模板参数确是不同的;

模板参数

map

template <class K, class V>  map模板参数
	
typedef pair<const K, V> T;  map中typedef的T

RBTree<const K, T, mapKofT> _tree;  map的成员变量

set

template <class K>   set模板参数

RBTree<K, K, setKofT> _tree;   set成员变量

可以看到我们set传递给RBTree的是两个K类型,而这个K类型又是set需要存储数据的类型,我们再看map,map也一样它传递给RBTree的是K和pair<K,V>类型,其中的pair<K,V>可以代表map存储的数据类型;为什么要这么传递类型呢?当然是为了使得RBTree可以同时适配map和set两种容器啦

追本溯源我们去看底层的RBTree的实现;

RBTree的底层实现是这样的:

template <class T>   红黑树的节点
struct RBTreeNode
{
	typedef RBTreeNode<T> node;
	node* _left;
	node* _right;
	T _data;
	node* _parent;
	Color _cor;
	RBTreeNode<T>(const T& data)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _cor(Red)
		, _data(data)
	{}
};

template<class K, class T, class KofT>   红黑树的接收的模板参数
class RBTree
{
    typedef RBTreeNode<T> node;  typedef红黑树的节点
};

 可以看到红黑树底层统一是使用第二个模板参数T来接收map和set的数据的,因为map是KV模型是具有两个类型的,而一个参数无法传递两个类型,如果我们要传递两个参数的话,我们的set又是K模型,只有一个参数类型,所以为了使得RBTree既可以接收map数据类型又可以接收set数据类型,我们使得map传递的T参数是pair类型,这样我们就可以让红黑树可以使用一个模板参数来同时接收map和set的数据了;这样我们的RBTree的节点也可以这样形成了;

现在我们知道了map与set这样传模板参数的原因,这个KofT仿函数吧;

KofT仿函数

mapKofT

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

setKofT

	struct setKofT
	{
		const K& operator ()(const K& t)
		{
			return t;
		}
	};

我们可以看到KofT顾名思义就是在类型T中存储的我们的K类型,在我们的map中T是pair<K,V>类型的,K被包含在了T中,所以我们可以通过这个仿函数来获得map存储的第一个数据;而set中,数据类型T其实就是是K,所以KofT对于set来说就是返回T自己这个类型而已;这里我们可以看作是set为map做出的让步;

那map获得这个第一个数据有什么用呢?

我们把目光放到插入数据的接口处,在我们插入数据时,是不是每次都需要比较数据的大小之后才会插入数据;而比较数据set可以直接使用T来比较,但是map不可以,map的T是pair类型,map想用来比较的是第一个数据K所以,我们不能使用T来直接比较;我们得需要使用仿函数KofT来获得这第一个参数来比较,set底层的KofT返回的就是T所以不影响比较;

insert中寻找位置时的代码

node* cur = _root;
node* parent = cur;
KofT koft;

while (cur)    
{
	if (koft(data) < koft(cur->_data))  我们这里就是使用koft来进行比较的
	{
		parent = cur;
		cur = cur->_left;
	}
	else if (koft(data) > koft(cur->_data))
	{
		parent = cur;
		cur = cur->_right;
	}
	else
	{
		return make_pair(iterator(cur), false);
	}
}

这也就是KofT的作用了;

compare仿函数

其实RBTree在stl的底层实现上是有四个模板参数的

template<class K, class T, class KofT, class compare>

这个仿函数是用来控制比较的方式的,这样我们的排序顺序就会随着仿函数的变化而改变;一样的我们将compare加在insert接口的比较处就好了;

if(koft(data) < koft(cur->_data))

改变为

compare(koft(data) , koft(cur->_data))

这样就可以控制我们的插入方式,使得大的数据插入到树的左枝小的插入到右枝;

iterator迭代器

我们map与set的迭代器是调用了RBTree的接口的;

注意:我们这里调用其他类模板中的内置类型的时候需要加上typename,因为此时我们的编译器是无法识别类型和静态变量的;

map

	typedef typename RBTree<const K, T, mapKofT>::iterator iterator;
	typedef typename RBTree<const K, T, mapKofT>::const_iterator const_iterator;

set

	typedef typename RBTree<K, K, setKofT>::const_iterator iterator;
	typedef typename RBTree<K, K, setKofT>::const_iterator const_iterator;

由于map与set的key值都不允许修改;所以在迭代器传参和使用上有着一些处理;map中因为key值不允许修改,所以传递给迭代器的变量都是const类型的,而set的处理就是const迭代器和普通迭代器都是const迭代器,这样set的数据都是不允许通过迭代器来修改的了;

我们现在来看迭代器的实现;

template<class T, class Ref, class Ptr>//迭代器
struct RBTree_iterator
{
	typedef RBTree_iterator<T, Ref, Ptr> self;
	typedef RBTree_iterator<T, T&, T*>iterator;//普通迭代器类型
	typedef RBTreeNode<T> node;

	RBTree_iterator (node* newnode)
		:_node(newnode)
	{}
										//这里的iterator是普通迭代器类型
	RBTree_iterator (const iterator& it)//当传过来的是普通迭代器的时候是拷贝构造(支持普通迭代器转换为const迭代器)
		: _node(it._node)			    //穿过来的是const迭代器的时候是编译器自动形成的构造函数来构造的			
	{}


	self &operator ++()
	{
		if (_node == nullptr)
			return *this;
		node* subr = _node->_right;
		if (subr)//如果右节点不为空
		{
			while (subr->_left)//找最左节点
			{
				subr = subr->_left;
			}
			_node = subr;
		}
		else//如果右节点为空
		{
			node* parent = _node->_parent;
			while (parent&& _node == parent->_right)//我要是我父亲的左节点才退出
			{
				_node = _node->_parent;
				parent = parent->_parent;
			}
			_node = parent;//这个父亲就是我们需要的位置
		}
		return *this;
	}

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

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

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

	node* _node;
};

template<class K, class T, class KofT>
class RBTree
{
    红黑树中定义的迭代器
	typedef RBTree_iterator<T, T&, T*> iterator;
	typedef RBTree_iterator<T, const T&,const T*> const_iterator;
}

我们之前早在list的容器那就实现过const迭代器与非const迭代器的的实现,所以这里迭代器的具体实现我们不做过多解释,如果不理解可以看我们前面的博客:

C++stl_list 链表-CSDN博客

operator++

我们只解释一下++代码的实现++代码中,我们迭代器所在节点的下一节点要么是我们右边的最左节点,要么就是我们是父亲节点左节点的父亲,这样的++方式即可满足++,看代码实现辅助理解;

接下来我们看红黑树中对迭代器接口的实现:

iterator begin()
{
	if (_root == nullptr)
		return nullptr;
	node* cur = _root;
	while (cur->_left)
	{
		cur = cur->_left;
	}
	return iterator(cur);
}

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

const_iterator begin()const
{
	if (_root == nullptr)
		return nullptr;
	node* cur = _root;
	while (cur->_left)
	{
		cur = cur->_left;
	}
	return iterator(cur);
}

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

可以看到我们的begin()接口获得的就是我们树的最左节点,end()接口获得的就是最右节点的下一个节点(空节点);

在我们实现了这些之后,我们还需要注意的地方就是,我们set中获得的RBTree中的迭代器都是const_iterator,但是可是普通的set的set的成员变量RBTree中的iterator是普通迭代器;所以会发生这样的问题:

 我们现在来看iterator是如何操作的:

template<class T, class Ref, class Ptr>//迭代器
struct RBTree_iterator
{
	typedef RBTree_iterator<T, Ref, Ptr> self;
	typedef RBTree_iterator<T, T&, T*>iterator;//普通迭代器类型
	typedef RBTreeNode<T> node;

	RBTree_iterator (node* newnode)
		:_node(newnode)
	{}
										//这里的iterator是普通迭代器类型
	RBTree_iterator (const iterator& it)//当传过来的是普通迭代器的时候是拷贝构造(支持普通迭代器转换为const迭代器)
		: _node(it._node)			    //穿过来的是const迭代器的时候是编译器自动形成的构造函数来构造的			
	{}

在迭代器中新增了一个支持接收普通迭代器的构造函数,使得我们在迭代器是const迭代器的时候;也可以接收普通迭代器变量作为参数来构造我们的迭代器;

map中的operator[]

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

我们红黑树中的insert返回值不再是bool而是一个pair<iterator,bool>类型;这个返回值的作用在前面的使用就已经说过了,所以这里就不过多讲解了;

以上就是大致的map和set的内容;

如果需要查看完整的代码可以到这个链接处查看:

模拟实现stl容器/模拟实现map与set · future/my_road - 码云 - 开源中国 (gitee.com)

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

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

相关文章

#新版Onenet云平台使用(ESP8266 AT指令上报数据以及公网MQTT服务器连接测试)

1.上云方式&#xff1a;MQTT 参考&#xff1a; 新版ONENET物联网开放平台ATMQTT指令连接_at指令连接onenet的mqtt-CSDN博客https://blog.csdn.net/lilbye/article/details/131770196 ESP8266-01s入门&#xff1a;AT指令讲解、上云与MQTT通信教程-物联沃-IOTWORD物联网https:…

软考 系统架构设计师系列知识点之大数据设计理论与实践(5)

接前一篇文章&#xff1a;软考 系统架构设计师系列知识点之大数据设计理论与实践&#xff08;4&#xff09; 所属章节&#xff1a; 第19章. 大数据架构设计理论与实践 第3节 Lambda架构 19.3.1 Lambda架构对大数据处理系统的理解 Lambda架构由Storm的作者Nathan Marz提出&…

SpringCloud实用篇(四)——Nacos

Nacos nacos官方网站&#xff1a;https://nacos.io/ nacos是阿里巴巴的产品&#xff0c;现在是springcloud的一个组件&#xff0c;相比于eureka的功能更加丰富&#xff0c;在国内备受欢迎 nacos的安装 下载地址&#xff1a;https://github.com/alibaba/nacos/releases/ 启动…

vscode远程连接centos

文章目录 vacode连接linux1. 安装插件2. 查看配置3. 打开ssh4. 远程连接 vacode连接linux 1. 安装插件 在扩展栏搜索remote &#xff0c;找到Remote Development插件&#xff0c;进行安装&#xff1a; 2. 查看配置 打开自己的linux终端&#xff0c;输入ifconfig&#xff0c;…

BackTrader 中文文档(九)

原文&#xff1a;www.backtrader.com/ 期货和现货补偿 原文&#xff1a;www.backtrader.com/docu/order-creation-execution/futurespot/future-vs-spot/ 发布1.9.32.116添加了对在社区中提出的一个有趣用例的支持。 通过未来开始交易&#xff0c;其中包括实物交割 让指标告诉…

怎么做预约小程序_探索我们的全新预约小程序

在繁忙的现代生活中&#xff0c;无论是想预约一次美容护理&#xff0c;还是预定一家心仪的餐厅&#xff0c;亦或是安排一次专业的咨询服务&#xff0c;我们都希望能够在最短的时间内完成这些操作&#xff0c;节省时间和精力。如今&#xff0c;一款全新的预约小程序应运而生&…

SSH安全设置

今天发现自己的公有云服务器被攻击了 然后查看了登录日志&#xff0c;如上图 ls -sh /var/log/secure vim /var/log/secure然后增加了安全相关的设置 具体可以从以下方面增加安全性&#xff1a; 修改默认SSH端口公有云修改安全组策略及防火墙端口设置登录失败次数锁定用户及限…

亚马逊CloudFront使用体验

前言 首先在体验CloudFront之前&#xff0c;先介绍一下什么是CDN&#xff0c;以及CDN的基本原理。 CDN是Content Delivery Network&#xff08;内容分发网络&#xff09;的缩写&#xff0c;是一种利用分布式节点技术&#xff0c;在全球部署服务器&#xff0c;即时地将网站、应…

LSTM 循环神经网络原理深度解读与网络结构精细剖析

长短期记忆网络&#xff08;Long Short-Term Memory, LSTM&#xff09;是一种特殊的循环神经网络&#xff08;Recurrent Neural Network, RNN&#xff09;&#xff0c;设计用于解决长期依赖问题&#xff0c;特别是在处理时间序列数据时。 循环神经网络&#xff08;RNN&#xf…

每日一VUE——组件的生命周期

文章目录 VUE组件的生命周期生命周期钩子函数实例创建Teleport VUE组件的生命周期 组件生命周期 组件从创建到挂载、更新、到销毁的一系列过程被称为组件的生命周期。 生命周期函数 在组件的各个生命周期节点执行的函数&#xff0c;为生命周期钩子函数。 生命周期钩子函数…

RT-thread信号量与互斥量

1,信号量 信号量是一种轻型的用于解决线程间同步问题的内核对象,线程可以获取或释放它,从而达到同步或互斥的目的。理解资源计数适合于线程间工作处理速度不匹配的场合;信号量在大于0时才能获取,在中断、线程中均可释放信号量。 为了体现使用信号量来达到线程间的同步,…

【STM32】西南交大嵌入式系统设计实验:环境配置

把走过的坑记录一下&#xff0c;希望后来人避坑 No ST-Link device detected.问题解决 如果跟着指导书出现这个问题&#xff1a; 直接跳过这一步不用再更新固件&#xff0c;后面直接创建项目写程序就行了。 在keil里配置成用DAP_link即可。 详细的可以看这篇文章&#xff1a…

NumPy基础及取值操作

文章目录 第1关&#xff1a;ndarray对象第2关&#xff1a;形状操作第3关&#xff1a;基础操作第4关&#xff1a;随机数生成第5关&#xff1a;索引与切片 第1关&#xff1a;ndarray对象 编程要求 根据提示&#xff0c;在右侧编辑器Begin-End中填充代码&#xff0c;根据测试用例…

Oracle 数据库 count的优化-避免全表扫描

Oracle 数据库 count的优化-避免全表扫描 select count(*) from t1; 这句话比较简单&#xff0c;但很有玄机&#xff01;对这句话运行的理解&#xff0c;反映了你对数据库的理解深度&#xff01; 建立实验的大表他t1 SQL> conn scott/tiger 已连接。 SQL> drop table …

Fiddler工具的操作和功能时-----定位到步骤图(助力抓包)

前言&#xff1a; 继续上一篇&#xff0c;已经对fiddler的安装、配置和代理的问题进行了讲解&#xff1a; Fiddle配置代理&#xff0c;保手机模拟器访问外部网络-CSDN博客 本章&#xff0c;讲对一些fiddler的操作进行一系列讲解&#xff01;Fiddler作为一款网络调试工具&…

可视化大屏的应用(13):3D建模运用到机房运维中

可视化大屏在机房运维中发挥着重要的作用&#xff0c;主要体现在以下几个方面&#xff1a; 实时监控 通过可视化大屏&#xff0c;可以实时监控机房的各项指标和状态&#xff0c;如服务器的运行状态、网络流量、机房温度等。运维人员可以通过大屏快速获取关键信息&#xff0c;…

LazyVim开发vue2

neovim 0.5刚出来的时代&#xff0c;那时刚有lua插件我很狂热。每天沉迷于打造自己的IDE之中。写过一堆相关的博客&#xff0c;也录过一些视频教程。后来发现neovim的接口和插件更新的很快&#xff0c;导致配置文件要不定期的修改&#xff0c;才能保证新版本的插件的适配。我也…

程序员学CFA——数量分析方法(三)

数量分析方法&#xff08;三&#xff09; 概率论基础概率论的基本概念概率论的相关术语随机变量结果随机事件 事件之间的关系互斥事件遍历事件独立事件 概率的定义与确定方法概率的定义概率的确定方法赔率条件概率 概率的计算乘法法则与加法法则联合概率与乘法法则加法法则 全概…

LeetCode257:二叉树的所有路径

题目描述 给你一个二叉树的根节点 root &#xff0c;按 任意顺序 &#xff0c;返回所有从根节点到叶子节点的路径。 叶子节点 是指没有子节点的节点。 解题思想 利用了回溯 代码 class Solution { public:void traversal(TreeNode* node, vector<int> &path, vect…