作者:小树苗渴望变成参天大树
作者宣言:认真写好每一篇博客
作者gitee:gitee✨
作者专栏:C语言,数据结构初阶,Linux,C++ 动态规划算法
如 果 你 喜 欢 作 者 的 文 章 ,就 给 作 者 点 点 关 注 吧!
今天我们开始讲解进阶中的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在文档的中的具体介绍吧
T: set中存放元素的类型,实际在底层存储
Compare:set中元素默认按照小于来比较
Alloc:set中元素空间的管理方式,使用STL提供的空间配置器管理
我们看看提供了一个仿函数,原因是set在插入的时候需要进行内部的比较,每个类的比较的比较方式不一样,所以需要提供仿函数,来实现我们自己的比较逻辑。set的模板参数还是比较好理解,接下来我就带大家来看看set有哪些接口,具体是怎么使用吧.
我们只需要研究构造函数,赋值和析构可以不用了解,都是和之前的一样的
这就是我们三种构造函数,通过监视窗口我们有没有发现一些细节的问题,我们看到v里面原本乱序的数据在s2中就编程有序了,而且原来有重复数据,而s2里面叶没有重复数据了,所以这是set一个比较需要了解的知识点,原因是set底层是类似于二叉搜索数的实现原理,才会变成有序的,但set里面有相同数据,就不允许插入了,在二叉搜索数的时候就已经知道怎么做了,这是set,也是map的特性----去重和排序
迭代器这部分没有什么好讲解的,迭代器设计初衷,就是为了是所有容器的遍历都是一样的,博主就带大家过一遍这个迭代器的使用
我们看到排序了又去重了,我们看看修改数据会发生什么??
我们看到不允许进行修改,原因是修改会破坏二叉搜索树的结构,如果想要修改一个数据可以先删除,在插入想要修改后的数据。
我们发现他的迭代器都是使用const修饰的,所以可以控制数据不会被修改,其他的迭代器都是和之前一眼固定用法,我也不做过多的演示了,大家感兴趣自己下去实验一下。
插入函数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++;
}
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章节在细说
这两个在set里面含义是一样,都是获取key的值,在map里面就不一样
这三个函数大家最熟只不过了,也不做演示了。
这几个函数我要做一下特别介绍,因为这才是set和其他STL容器的不同.
找到了就返回迭代器位置,没找到就返回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大的迭代器位置返回。
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的主要功能是判断在不在。
学习了set我们再学习map其实难度会好很多,我们的map是一个key_value模型,两者是绑定的。我们的map和set一样可以去重,他的key只能由一个,但是value不是唯一。
接下来博主会介绍圈主的部分,五个操作函数的使用和set是一样的,传key的值就可以了,这个大家下来看看文档就可以理解了
之前再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调用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的多参数隐式类型转换
在这里插入图片描述
我们看到map也实现了去重,当key有相同的就不插入,不覆盖,插入过程值比较key,value相同无所谓,make_pair也是我们平时使用最多的,而且也不能对插入后的key进行修改
其次我们看到插入也会返回一个pair,一个是插入位置的迭代器,一个是插入是否成功。
这个大家一定要先理解,因为一会讲解的[]就和这个有关,先给大家铺垫一个比较好理解的,才能更好的接受不太好理解的
只需要传key的值就可以了。博主就不做具体演示了。
接下来带大家来看看之前的题目,大家还记得统计水果次数的功能吗??大家可以看看之前写的二叉搜索树,今天我们有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就可以了,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;
}
};
这个题目大家非常熟悉不过了,因为需要把括号两边进行绑定,所以需要使用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树了,难度有所提升,希望大家可以好好理解,加深理解,今天讲的对后面的模拟实现会很有帮助的,我们下篇再见