【C++】- set和map的具体使用(multiset和multimap的介绍)

【C++】- set和map的具体使用(multiset和multimap的介绍)_第1张图片
作者:小树苗渴望变成参天大树
作者宣言:认真写好每一篇博客
作者gitee:gitee✨
作者专栏:C语言,数据结构初阶,Linux,C++ 动态规划算法
如 果 你 喜 欢 作 者 的 文 章 ,就 给 作 者 点 点 关 注 吧!

文章目录

  • 前言
  • 一、set
  • 二、set的接口以及使用方法
    • 2.1构造函数
    • 2.2迭代器
    • 2.3修改函数接口
    • 2.4操作函数
  • 三、map
    • 3.1插入和删除函数
    • 3.2operator[]函数
  • 四、set和map的应用
    • 4.1[349. 两个数组的交集](https://leetcode.cn/problems/intersection-of-two-arrays/description/)
    • 4.2[有效的括号](https://leetcode.cn/problems/valid-parentheses/description/)


前言

今天我们开始讲解进阶中的STL,这个容器相比较前面而言结构更加的复杂,但是用处也是更加的多,大家还记得我进阶的上一篇博客写的是什么吗??是二叉搜索树,所以今天这篇讲的STL就和上一篇博客讲的结构有关系,大家最好先提前看看二叉搜索树的博客,才能理解一些接口为什么会出现这样的效果,话不多说,我们开始介绍两个STL的容器-----set和map


在初阶阶段,我们已经接触过STL中的部分容器,比如:vector、list、deque、
forward_list(C++11)等,这些容器统称为序列式容器,因为其底层为线性序列的数据结构,里面存储的是元素本身。那什么是关联式容器?它与序列式容器有什么区别?

关联式容器也是用来存储数据的,与序列式容器不同的是,其里面存储的是结构的键值对,在数据检索时比序列式容器效率更高

键值对
用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量key和value,key代表键值,value表示与key对应的信息。比如:现在要建立一个英汉互译的字典,那该字典中必然有英文单词与其对应的中文含义,而且,英文单词与其中文含义是一一对应的关系,即通过该应该单词,在词典中就可以找到与其对应的中文含义

template <class T1, class T2>
struct pair
{
	typedef T1 first_type;
	typedef T2 second_type;
	T1 first;
	T2 second;
	pair(): first(T1()), second(T2())
	{}
	pair(const T1& a, const T2& b): first(a), second(b)
	{}
};

根据应用场景的不桶,STL总共实现了两种不同结构的管理式容器:树型结构与哈希结构。树型结构的关联式容器主要有四种:map、set、multimap、multiset。这四种容器的共同点是:使用平衡搜索树(即红黑树)作为其底层结果,容器中的元素是一个有序的序列。下面一依次介绍每一个容器。

注意:set就是key的模型,map就是key_value的模型

一、set

注意:

  1. 与map/multimap不同,map/multimap中存储的是真正的键值对,set中只放value,但在底层实际存放的是由构成的键值对。
  2. set中插入元素时,只需要插入value即可,不需要构造键值对。
  3. set中的元素不可以重复(因此可以使用set进行去重)。
  4. 使用set的迭代器遍历set中的元素,可以得到有序序列
  5. set中的元素默认按照小于来比较
  6. set中查找某个元素,时间复杂度为: l o g 2 n log_2 n log2n
  7. set中的元素不允许修改(为什么?)一会后面会说到
  8. set中的底层使用二叉搜索树(红黑树)来实现

我们一起来看看set在文档的中的具体介绍吧
【C++】- set和map的具体使用(multiset和multimap的介绍)_第2张图片
T: set中存放元素的类型,实际在底层存储的键值对。
Compare:set中元素默认按照小于来比较
Alloc:set中元素空间的管理方式,使用STL提供的空间配置器管理

我们看看提供了一个仿函数,原因是set在插入的时候需要进行内部的比较,每个类的比较的比较方式不一样,所以需要提供仿函数,来实现我们自己的比较逻辑。set的模板参数还是比较好理解,接下来我就带大家来看看set有哪些接口,具体是怎么使用吧.

二、set的接口以及使用方法

2.1构造函数

我们只需要研究构造函数,赋值和析构可以不用了解,都是和之前的一样的
在这里插入图片描述
【C++】- set和map的具体使用(multiset和multimap的介绍)_第3张图片
【C++】- set和map的具体使用(multiset和multimap的介绍)_第4张图片

这就是我们三种构造函数,通过监视窗口我们有没有发现一些细节的问题,我们看到v里面原本乱序的数据在s2中就编程有序了,而且原来有重复数据,而s2里面叶没有重复数据了,所以这是set一个比较需要了解的知识点,原因是set底层是类似于二叉搜索数的实现原理,才会变成有序的,但set里面有相同数据,就不允许插入了,在二叉搜索数的时候就已经知道怎么做了,这是set,也是map的特性----去重和排序

2.2迭代器

【C++】- set和map的具体使用(multiset和multimap的介绍)_第5张图片
迭代器这部分没有什么好讲解的,迭代器设计初衷,就是为了是所有容器的遍历都是一样的,博主就带大家过一遍这个迭代器的使用
【C++】- set和map的具体使用(multiset和multimap的介绍)_第6张图片
我们看到排序了又去重了,我们看看修改数据会发生什么??
【C++】- set和map的具体使用(multiset和multimap的介绍)_第7张图片

我们看到不允许进行修改,原因是修改会破坏二叉搜索树的结构,如果想要修改一个数据可以先删除,在插入想要修改后的数据。

为什么不可以进行修改呢,是怎么控制的?
【C++】- set和map的具体使用(multiset和multimap的介绍)_第8张图片

我们发现他的迭代器都是使用const修饰的,所以可以控制数据不会被修改,其他的迭代器都是和之前一眼固定用法,我也不做过多的演示了,大家感兴趣自己下去实验一下。

2.3修改函数接口

【C++】- set和map的具体使用(multiset和multimap的介绍)_第9张图片
插入函数insert:
【C++】- set和map的具体使用(multiset和multimap的介绍)_第10张图片
这个pair一会将set的最后一个函数的时候在细讲!!

	set<int> s;
	s.insert(4);
	s.insert(3);
	s.insert(6);
	s.insert(2);
	s.insert(3);
	set<int>::iterator it = s.begin();
	for (auto e : s)
	{
		cout << *it << " ";
		it++;
	}

【C++】- set和map的具体使用(multiset和multimap的介绍)_第11张图片
这和我们构造函数看到的效果是一样的。

删除函数erase:
【C++】- set和map的具体使用(multiset和multimap的介绍)_第12张图片

	cout << endl;
	s.erase(1);
	s.erase(2);
	s.erase(3);
	it = s.begin();
	for (auto e : s)
	{
		cout << *it << " ";
		it++;
	}

【C++】- set和map的具体使用(multiset和multimap的介绍)_第13张图片
这个删除的效果就很简单,有就删除,没有就不做任何处理

后面几个就不做介绍,clear和swap大家肯定都会,最后两个等到C++11章节在细说

2.4操作函数

在这里插入图片描述

这两个在set里面含义是一样,都是获取key的值,在map里面就不一样

在这里插入图片描述

这三个函数大家最熟只不过了,也不做演示了。

【C++】- set和map的具体使用(multiset和multimap的介绍)_第14张图片

这几个函数我要做一下特别介绍,因为这才是set和其他STL容器的不同.


find:
【C++】- set和map的具体使用(multiset和multimap的介绍)_第15张图片

找到了就返回迭代器位置,没找到就返回end的位置,左闭右开,所以是最后一个元素后面的一个位置

具体使用:
【C++】- set和map的具体使用(multiset和multimap的介绍)_第16张图片

我们看到set自带的和库里面的查找函数都可以完成查找的功能,为什么set还要单独在设计一个查找函数的,原因是,set的查找函数的左右找,不是像库里面的一个个的遍历查找,所以自带的复杂度可以达到O(logn),而库里面的只有O(n)。


count:
在这里插入图片描述
这个函数对于set其他意义不大,他是统计数据出现的次数,而set里面的数据没有重复的,所以只有1个,在set中这个函数可以判断一个数据在不在
在这里插入图片描述
那这个函数的意义在哪呢,我们讲完剩下来的三个函数在具体说。


lower_bound和upper_bound:
【C++】- set和map的具体使用(multiset和multimap的介绍)_第17张图片
正常情况这两个函数是同时使用的,一个用来找左边界,一个用来找右边界
【C++】- set和map的具体使用(multiset和multimap的介绍)_第18张图片

lower_bound是找到大于等于val值的迭代器
upper_bound是找到大于val值的迭代器

原因是他要配合插入删除来使用,因为插入和删除都可以使用迭代器来操作,而且是左闭右开的,所以upper_bound是返回比val大的迭代器位置返回。


equal_range:
在这里插入图片描述

在这里插入图片描述

pair其实是一个类模板结构,这个类里面右两个变量,把两个值放在一个结构里面了,一会再讲map的时候会讲为什么会这样设计。

具体使用:
【C++】- set和map的具体使用(multiset和multimap的介绍)_第19张图片

我们返回的是返回的是一个pair的结构,所以第一个数是返回的是val的迭代器,而第二个返回的是最后依次出现val值后一位值的迭代器,再set当中体现不出来


multiset:
上面的count和equal_range其实是为multiset设计的,这个STL有什么功能呢,相比较于set这个函数不去重

【C++】- set和map的具体使用(multiset和multimap的介绍)_第20张图片

通过上面的图我们发现count可以统计val出现了几次,equal_range的first返回第一次val出现的迭代器,second返回最后一个val出现的下一个位置,其实是为了插入和删除的,这个大家要明白.

注意: multiset的其他使用和set是一样的,我们大部分情况都是使用set这个容器,其次set的主要功能是判断在不在。

三、map

在这里插入图片描述
学习了set我们再学习map其实难度会好很多,我们的map是一个key_value模型,两者是绑定的。我们的map和set一样可以去重,他的key只能由一个,但是value不是唯一。

接下来就带大家看看由哪些接口函数吧
【C++】- set和map的具体使用(multiset和multimap的介绍)_第21张图片

接下来博主会介绍圈主的部分,五个操作函数的使用和set是一样的,传key的值就可以了,这个大家下来看看文档就可以理解了

3.1插入和删除函数

之前再set插入的时候返回值就是pair,为什么要设计程这样,我们知道,key_value模型,是讲着两个值进行绑定在一起,所以再插入或者删除的是都是一起操作的,既然这样,我们把这两个值放到一个结构体里面,通过控制这个结构体来控制这两个值,所以pair就是相当于这样的一个作用,我们来看一下插入函数

insert:
【C++】- set和map的具体使用(multiset和multimap的介绍)_第22张图片
我们插入的时候也是插入一个结构体对象的引用,我们来具体来看看怎么使用:

	map<int, string> m1;
	pair<int, string> p(1, "数字1");//最基本的插入方式
	m1.insert(p);

	m1.insert(make_pair(2, "数字2"));//make_pair调用pair

	m1.insert({ 3, "数字3" });//c++11的多参数隐式类型转换
	m1.insert({ 3, "数字3" });//c++11的多参数隐式类型转换
	m1.insert({ 3, "数字3" });//c++11的多参数隐式类型转换
	
	m1.insert({ 4, "数字3" });//c++11的多参数隐式类型转换

在这里插入图片描述
【C++】- set和map的具体使用(multiset和multimap的介绍)_第23张图片
我们看到map也实现了去重,当key有相同的就不插入,不覆盖,插入过程值比较key,value相同无所谓,make_pair也是我们平时使用最多的,而且也不能对插入后的key进行修改


其次我们看到插入也会返回一个pair,一个是插入位置的迭代器,一个是插入是否成功。
【C++】- set和map的具体使用(multiset和multimap的介绍)_第24张图片
这个大家一定要先理解,因为一会讲解的[]就和这个有关,先给大家铺垫一个比较好理解的,才能更好的接受不太好理解的

erase:
【C++】- set和map的具体使用(multiset和multimap的介绍)_第25张图片

只需要传key的值就可以了。博主就不做具体演示了。

3.2operator[]函数

接下来带大家来看看之前的题目,大家还记得统计水果次数的功能吗??大家可以看看之前写的二叉搜索树,今天我们有map就很简单我们来看看代码:

	//第一种
	map<string, int> countMap;
	string arr[] = { "苹果","香蕉","苹果","梨子","苹果","香蕉","梨子" };
	for (auto e : arr)
	{
		auto it = countMap.find(e);
		if (it == countMap.end())
		{
			countMap.insert(make_pair(e, 1));
		}
		else
		{
			it->second++;
		}
	}
	map<string, int>::iterator it = countMap.begin();
	for (auto e : countMap)
	{
		cout << it->first << " " << it->second << endl;
		it++;
	}
	
	//第二种
	cout << endl;
	map<string, int> countMap1;
	string arr1[] = { "苹果","香蕉","苹果","梨子","苹果","香蕉","梨子" };
	for (auto e : arr1)
	{
		countMap1[e]++;
	}
	map<string, int>::iterator it1 = countMap1.begin();
	for (auto e : countMap1)
	{
		cout << it1->first << " " << it1->second << endl;
		it1++;
	}

【C++】- set和map的具体使用(multiset和multimap的介绍)_第26张图片
通过第一种的思路,我们第二种看着很简单,但是里面的核心思路还是和第一种一样,内部是插入操作,我们来通过文档看看这个[]实现的原理是什么??

我们来看文档:
【C++】- set和map的具体使用(multiset和multimap的介绍)_第27张图片

大家还记得我们插入操作的时候研究insert返回值的的案例吗。和这个一模一样。
【C++】- set和map的具体使用(multiset和multimap的介绍)_第28张图片
此时大家应该明白[]的工作原理了吧,其实就要搞懂调用关系以及pair的特性,这个再模拟实现的时候会更清楚的,大家先理解着。


接下来就是multimap这个容器,他这个容器和map几乎一模一样,他不可以去重,所以[]这个函数他就没有,其余的使用和毛一样,博主就不做具体介绍了,接下来博主来使用set和map带啊大家来解决原来比较难解决的题目

注意:map是用来统计次数的,也可以判断数据在不在的,map的使用度更广泛

四、set和map的应用

4.1349. 两个数组的交集

这个题目也是很经典的,接下来我讲带大家怎么来解决这个问题。
【C++】- set和map的具体使用(multiset和multimap的介绍)_第29张图片
所以这题我们不需要统计次数,所以使用set就可以了,set可以去重也可以排序,所以符合上图的分析思路,来看代码:

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        vector<int>v;
        set<int> s1(nums1.begin(),nums1.end());
        set<int> s2(nums2.begin(),nums2.end());
        
        auto it1=s1.begin();
        auto it2=s2.begin();
        while(it1!=s1.end()&&it2!=s2.end())
        {
            if(*it1<*it2)
            {
                it1++;
            }
            else if(*it1>*it2)
            {
                it2++;
            }
            else
            {
                v.push_back(*it1);
                it1++;
                it2++;
            }
        }
        return v;
    }
};

4.2有效的括号

这个题目大家非常熟悉不过了,因为需要把括号两边进行绑定,所以需要使用map去操作,这样题目说有更多的括号都不用担心,再前面进行修改就好了。

class Solution {
public:
    bool isValid(string s) {
    stack<char> st;
    map<char,char> m;
    m['(']=')';
    m['{']='}';
    m['[']=']';
    for(auto e:s)
    {
        if(m.count(e))
        {
            st.push(e);
        }
        else
        {
            if(st.empty())
            {
                return false;
            }
            else
            {
                char top=st.top();
                st.pop();
                if(m[top]!=e)
                {
                   return false;
                }
            }
        }
    }
         return st.empty();
    }
};

通过上面两个应用大家对set和map应该了解了。接下来我将给大家讲解AVL树了,难度有所提升,希望大家可以好好理解,加深理解,今天讲的对后面的模拟实现会很有帮助的,我们下篇再见

你可能感兴趣的:(C++进阶,c++,开发语言)