【C++】STL——set和map及multiset和multimap的介绍及使用


🚀 作者简介:一名在后端领域学习,并渴望能够学有所成的追梦人。
🚁 个人主页:不 良
🔥 系列专栏:🛸C++  🛹Linux
📕 学习格言:博观而约取,厚积而薄发
🌹 欢迎进来的小伙伴,如果小伙伴们在学习的过程中,发现有需要纠正的地方,烦请指正,希望能够与诸君一同成长! 🌹


map和set底层结构都是二叉搜索树。

关联式容器

在前面学过的STL中的部分容器,比如:vector、list、deque、forward_list(C++11)等,这些容器统称为序列式容器,因为其底层为线性序列的数据结构,里面存储的是元素本身

关联式容器也是用来存储数据的,与序列式容器不同的是,其里面存储的是<key, value>结构的键值对,在数据检索时比序列式容器效率更高。本篇介绍的set和map就是关联式容器。

关联式容器中数据和数据之间有非常强的关联关系,不能随意插入。

键值对

用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量key和value,key代表键值,value表示与key对应的信息。

比如:现在要建立一个英汉互译的字典,那该字典中必然有英文单词与其对应的中文含义,而且英文单词与其中文含义是一一对应的关系,即通过输入单词,在词典中就可以找到与其对应的中文含义。

SGI-STL中关于键值对的定义:

template <class T1, class T2>
struct pair
{
   
   
	typedef T1 first_type;
	typedef T2 seco_type;
	T1 first;//key
	T2 second;//value
	pair():first(T1()),second(T2()){
   
   }//默认构造
	pair(const T1& a, const T2& b):first(a),second(b){
   
   }//构造函数
};

在上面的pair类中:

  • 是一个模板类,可以存放任意类型的键对值;
  • 有两个成员变量:first和second;

一般使用first表示key,second表示value。 键值对要在类外进行访问,所以使用struct定义类而不用class。

make_pair

该函数用来创建pair类型的匿名对象。

根据上面pair类的定义,我们要想创建一个pair类型的匿名对象需要像下面这样写:

pair<string,int>("苹果",1);

上面的方式还需要指定参数类型,所以为了方便,就封装了一个函数模板make_pair,用来快速创建一个匿名对象:

make_pair("苹果",1);

make_pair函数模板的定义如下:

image-20230725171320265

树形结构的关联式容器

根据应用场景的不同,STL总共实现了两种不同结构的管理式容器:树型结构与哈希结构。

树型结构的关联式容器主要有四种:map、set、multimap、multiset。这四种容器的共同点是:使用平衡搜索树(即红黑树)作为其底层结果,容器中的元素是一个有序的序列。 与树形结构容器不同的是,哈希结构容器中的元素是一个无序的序列。

set

认识set

  • set是按照一定次序存储元素的容器,使用set的迭代器遍历set中的元素,可以得到有序序列(默认是升序);

  • set虽然是采用了键值对的方式存储数据,但是在set中,value就是key,即相当于是K模型,并且每个value必须是唯一的,不可以重复,所以可以使用set进行去重;

  • 与map/multimap不同,map/multimap中存储的是真正的键值对<key, value>,set中只放value,但在底层实际存放的是由<value, value>构成的键值对,因此在set容器中插入元素时,只需要插入value即可,不需要构造键值对;

  • set中的元素不能在容器中修改(元素总是const),因为set在底层是用二叉搜索树来实现的,若是对二叉搜索树当中某个结点的值进行了修改,那么这棵树将不再是二叉搜索树。但是可以从容器中插入或删除它们;

  • 在内部,set中的元素总是按照其内部比较对象所指示的特定严格弱排序准则进行排序。当不传入内部比较对象时,set中的元素默认按照小于来比较(默认是升序);

  • set容器通过key访问单个元素的速度通常比unordered_set容器慢,但set容器允许根据顺序对元素进行直接迭代;

  • set在底层是用平衡搜索树(红黑树)实现的,所以在set当中查找某个元素的时间复杂度为logN。

set模板参数列表

image-20230725174304374

第一个模板参数T: set中存放元素的类型,实际在底层存储<value, value>的键值对;
Compare(仿函数):set中元素默认按照小于来比较(默认是升序);
Alloc:set中元素空间的管理方式,默认使用STL提供的空间配置器管理。

构造函数

image-20230725174650796

构造函数有三个,分别是默认构造函数、使用迭代器区间的构造函数、拷贝构造函数。

下面用代码来使用上面三个构造函数:

#include <iostream>
#include <set>
#include <vector>
using namespace std;
int main()
{
   
   
	set<int> s1;//默认构造函数
	vector<int> v = {
   
    66,5,4,11,66,66,77,8,9,22,10,99 };
	set<int> s2(v.begin(), v.end());//迭代器区间构造
	set<int> s3(s2);//拷贝构造
	//打印s1
	for (auto& e : s1)
	{
   
   
		cout << e << " ";
	}
	cout << endl;
	//s1内容为空
	//打印s2
	for (auto& e : s2)
	{
   
   
		cout << e << " ";
	}
	cout << endl;
	//s2打印出来为4 5 8 9 10 11 22 66 77 99
	
	//打印s3
	for (auto& e : s3)
	{
   
   
		cout << e << " ";
	}
	cout << endl;
	//s3打印出来为4 5 8 9 10 11 22 66 77 99
}

运行结果分析:

image-20230726075359482

根据结果我们可以看到set具有排序的功能(默认是升序),而且在数组中相同的元素经放在set容器之后就没有相同的元素了,说明了set具有去重的功能。set具有去重的功能,其底层是二叉搜索树,不能存放相同的元素。

迭代器

set的输出结果是升序的,其底层是二叉搜索树,打印二叉搜索树时使用中序打印出来的就是升序,所以set的迭代器在++时,移动的顺序就是中序遍历的顺序,所以使用迭代器遍历时得到的结果和二叉搜索树中序遍历得到的结果一致。

set迭代器的使用和之前学过的容器一样:

image-20230726080313203

具体使用示范代码:

#include <iostream>
#include <set>
#include <vector>
using namespace std;
int main()
{
   
   
	vector<int> v = {
   
    66,5,4,11,66,66,77,8,9,22,10,99 };
	set<int> s1(v.begin(), v.end());//迭代器区间构造
	//正向迭代器的使用
	set<int>::iterator it = s1.begin();
	while (it != s1.end())
	{
   
   
		cout << *it << " ";
		it++;
	}
	cout << endl;
	//反向迭代器的使用
	set<int>::reverse_iterator rit = s1.rbegin();
	while (rit != s1.rend())
	{
   
   
		cout << *rit << " ";
		++rit;
	}
	cout << endl;
}

运行结果分析:

image-20230726080905995

插入(insert)

image-20230726081526910

1、插入一个指定值并且返回一个键值对,返回的键值对中迭代器指向新插入的位置。

返回的是pair类型的对象,其中pair的第一个成员是迭代器,其指向的是插入的元素在set中的位置;第二个成员是bool类型,如果成功插入pair类中的第二个成员为true,插入失败pair类中的第二个成员为false。所以这里insert能够做到两个功能:查找和插入(查找成功返回false,查找失败返回true)。

pair<iterator,bool> insert (const value_type& val);

2、在指定的位置插入数据,并且返回插入的位置:

iterator insert (iterator position, const value_type& val);

3、将一段迭代器区间插入到set对象中:

template <class InputIterator>
  void insert (InputIterator first, InputIterator last);

向set中插入一个对象,并且能够保持结构不变,依然符合平衡搜索树。

代码示范:

#include <iostream>
#include <set>
#include <vector>
using namespace std;
int main()
{
   
   
	vector<int> v = {
   
    66,5,4,11,66,66,77,8,9,22,10,99 };
	set<int> s1(v.begin(), v.end());//迭代器区间构造
	//正向迭代器的使用
	set<int>::iterator it = s1.begin();
	while (it != s1.end())
	{
   
   
		cout << *it << " ";
		it++;
	}
	cout << endl;
	//构造一个pair类型的对象
	pair<set<int>::iterator, bool> p;
	//接收插入之后的返回值
	p = s1.insert(67);
	it = s1.begin();
	//打印插入的值和是否成功
	cout << *(p.first) << ":" << p.second << endl;

	p = s1.insert(67);
	//打印插入的值和是否成功
	cout << *(p.first) << ":" << p.second << endl;
}

打印结果:

image-20230726083811202

指定位置插入有可能会破环二叉搜索树的结构,所以不建议使用指定位置插入。

删除(erase)

image-20230726084117189

删除指定迭代器位置的值:

评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不 良

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值