💖作者:小树苗渴望变成参天大树🎈
🎉作者宣言:认真写好每一篇博客💤
🎊作者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代表键值,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
注意:
- 与map/multimap不同,map/multimap中存储的是真正的键值对<key, value>,set中只放value,但在底层实际存放的是由<value, value>构成的键值对。
- set中插入元素时,只需要插入value即可,不需要构造键值对。
- set中的元素不可以重复(因此可以使用set进行去重)。
- 使用set的迭代器遍历set中的元素,可以得到有序序列
- set中的元素默认按照小于来比较
- set中查找某个元素,时间复杂度为: l o g 2 n log_2 n log2n
- set中的元素不允许修改(为什么?)一会后面会说到
- set中的底层使用二叉搜索树(红黑树)来实现
我们一起来看看set在文档的中的具体介绍吧
T: set中存放元素的类型,实际在底层存储<value, value>的键值对。
Compare:set中元素默认按照小于来比较
Alloc:set中元素空间的管理方式,使用STL提供的空间配置器管理
我们看看提供了一个仿函数,原因是set在插入的时候需要进行内部的比较,每个类的比较的比较方式不一样,所以需要提供仿函数,来实现我们自己的比较逻辑。set的模板参数还是比较好理解,接下来我就带大家来看看set有哪些接口,具体是怎么使用吧.
二、set的接口以及使用方法
2.1构造函数
我们只需要研究构造函数,赋值和析构可以不用了解,都是和之前的一样的
这就是我们三种构造函数,通过监视窗口我们有没有发现一些细节的问题,我们看到v里面原本乱序的数据在s2中就编程有序了,而且原来有重复数据,而s2里面叶没有重复数据了,所以这是set一个比较需要了解的知识点,原因是set底层是类似于二叉搜索数的实现原理,才会变成有序的,但set里面有相同数据,就不允许插入了,在二叉搜索数的时候就已经知道怎么做了,这是set,也是map的特性----去重和排序
2.2迭代器
迭代器这部分没有什么好讲解的,迭代器设计初衷,就是为了是所有容器的遍历都是一样的,博主就带大家过一遍这个迭代器的使用
我们看到排序了又去重了,我们看看修改数据会发生什么??
我们看到不允许进行修改,原因是修改会破坏二叉搜索树的结构,如果想要修改一个数据可以先删除,在插入想要修改后的数据。
为什么不可以进行修改呢,是怎么控制的?
我们发现他的迭代器都是使用const修饰的,所以可以控制数据不会被修改,其他的迭代器都是和之前一眼固定用法,我也不做过多的演示了,大家感兴趣自己下去实验一下。
2.3修改函数接口
插入函数insert:
这个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++;}
这和我们构造函数看到的效果是一样的。
删除函数erase:
cout << endl;s.erase(1);s.erase(2);s.erase(3);it = s.begin();for (auto e : s){cout << *it << " ";it++;}
这个删除的效果就很简单,有就删除,没有就不做任何处理
后面几个就不做介绍,clear和swap大家肯定都会,最后两个等到C++11章节在细说
2.4操作函数
这两个在set里面含义是一样,都是获取key的值,在map里面就不一样
这三个函数大家最熟只不过了,也不做演示了。
这几个函数我要做一下特别介绍,因为这才是set和其他STL容器的不同.
find:
找到了就返回迭代器位置,没找到就返回end的位置,左闭右开,所以是最后一个元素后面的一个位置
具体使用:
我们看到set自带的和库里面的查找函数都可以完成查找的功能,为什么set还要单独在设计一个查找函数的,原因是,set的查找函数的左右找,不是像库里面的一个个的遍历查找,所以自带的复杂度可以达到O(logn),而库里面的只有O(n)。
count:
这个函数对于set其他意义不大,他是统计数据出现的次数,而set里面的数据没有重复的,所以只有1个,在set中这个函数可以判断一个数据在不在
那这个函数的意义在哪呢,我们讲完剩下来的三个函数在具体说。
lower_bound和upper_bound:
正常情况这两个函数是同时使用的,一个用来找左边界,一个用来找右边界
lower_bound是找到大于等于val值的迭代器
upper_bound是找到大于val值的迭代器
原因是他要配合插入删除来使用,因为插入和删除都可以使用迭代器来操作,而且是左闭右开的,所以upper_bound是返回比val大的迭代器位置返回。
equal_range:
pair其实是一个类模板结构,这个类里面右两个变量,把两个值放在一个结构里面了,一会再讲map的时候会讲为什么会这样设计。
具体使用:
我们返回的是返回的是一个pair的结构,所以第一个数是返回的是val的迭代器,而第二个返回的是最后依次出现val值后一位值的迭代器,再set当中体现不出来
multiset:
上面的count和equal_range其实是为multiset设计的,这个STL有什么功能呢,相比较于set这个函数不去重
通过上面的图我们发现count可以统计val出现了几次,equal_range的first返回第一次val出现的迭代器,second返回最后一个val出现的下一个位置,其实是为了插入和删除的,这个大家要明白.
注意: multiset的其他使用和set是一样的,我们大部分情况都是使用set这个容器,其次set的主要功能是判断在不在。
三、map
学习了set我们再学习map其实难度会好很多,我们的map是一个key_value模型,两者是绑定的。我们的map和set一样可以去重,他的key只能由一个,但是value不是唯一。
接下来就带大家看看由哪些接口函数吧
接下来博主会介绍圈主的部分,五个操作函数的使用和set是一样的,传key的值就可以了,这个大家下来看看文档就可以理解了
3.1插入和删除函数
之前再set插入的时候返回值就是pair,为什么要设计程这样,我们知道,key_value模型,是讲着两个值进行绑定在一起,所以再插入或者删除的是都是一起操作的,既然这样,我们把这两个值放到一个结构体里面,通过控制这个结构体来控制这两个值,所以pair就是相当于这样的一个作用,我们来看一下插入函数
insert:
我们插入的时候也是插入一个结构体对象的引用,我们来具体来看看怎么使用:
map<int, string> m1;pair<int, string> p(1, "数字1");//最基本的插入方式m1.insert(p);m1.insert(make_pair(2, "数字2"));//make_pair调用pairm1.insert({ 3, "数字3" });//c++11的多参数隐式类型转换m1.insert({ 3, "数字3" });//c++11的多参数隐式类型转换m1.insert({ 3, "数字3" });//c++11的多参数隐式类型转换m1.insert({ 4, "数字3" });//c++11的多参数隐式类型转换
在这里插入图片描述
我们看到map也实现了去重,当key有相同的就不插入,不覆盖,插入过程值比较key,value相同无所谓,make_pair也是我们平时使用最多的,而且也不能对插入后的key进行修改
其次我们看到插入也会返回一个pair,一个是插入位置的迭代器,一个是插入是否成功。
这个大家一定要先理解,因为一会讲解的[]就和这个有关,先给大家铺垫一个比较好理解的,才能更好的接受不太好理解的
erase:
只需要传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++;}
通过第一种的思路,我们第二种看着很简单,但是里面的核心思路还是和第一种一样,内部是插入操作,我们来通过文档看看这个[]实现的原理是什么??
我们来看文档:
大家还记得我们插入操作的时候研究insert返回值的的案例吗。和这个一模一样。
此时大家应该明白[]的工作原理了吧,其实就要搞懂调用关系以及pair的特性,这个再模拟实现的时候会更清楚的,大家先理解着。
接下来就是multimap这个容器,他这个容器和map几乎一模一样,他不可以去重,所以[]这个函数他就没有,其余的使用和毛一样,博主就不做具体介绍了,接下来博主来使用set和map带啊大家来解决原来比较难解决的题目
注意:map是用来统计次数的,也可以判断数据在不在的,map的使用度更广泛
四、set和map的应用
4.1349. 两个数组的交集
这个题目也是很经典的,接下来我讲带大家怎么来解决这个问题。
所以这题我们不需要统计次数,所以使用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树了,难度有所提升,希望大家可以好好理解,加深理解,今天讲的对后面的模拟实现会很有帮助的,我们下篇再见