文章目录
- 前言
- 一、关联式容器和序列式容器
- 二、set
- 1.set文档介绍
- 2.set成员函数
- 1.构造函数
- 2.迭代器
- 3.容量
- 4.修改
- 5.其他
- 三.multiset
- 四.map
- 1.map文档介绍
- 2.map成员函数
- 1.构造
- 2.insert插入
- 3.count
- 4.迭代器
- 5.【】和at
- 五.multimap
- 总结
前言
在本篇文章中,我们将会学到关联式容器set,multiset,map,multimap。
其中前两种容器对应我们上篇二叉树文章中的K模型,后两者容器对应我们的KV模型。
我们来详细看一下吧!!!
一、关联式容器和序列式容器
我们在之前学习到的vector,list,stack,queue等都属于序列式容器,底层是线性结构,只存储数据。
关联式容器也是也是用来存储数据的,不过存储的是<K,V>这样一个键值对。
二者有什么区别呢??
⭐️序列式容器中仅仅存储一个值,关联式容器中存储一个键值对
⭐️序列式容器内部不涉及排序,关联式容器内部自动排序
⭐️序列式容器的查找按照自身的值进行相关查找,关联式容器根据K的值进行查找。
并且关联式速度比序列式容器查找快很多。
键值对
键值对是由<K,V>两个值构成的,其中K叫做键值,V就是与之对应的数据。
比如在日常生活中我们用到的英汉字典,就是一个键值对,每个英文单词对应一个中文翻译。英文单词与其中文含义是一一对应的关系,即通过该应该单词,在词典中就可以找到与其对应的中文含义。
在C++中STL中已经帮助我们实现了键值对---->(pair)
T1和T2可以是两个不同的类型,访问是值是first,第二个值是second。
二、set
1.set文档介绍
看一下模板参数
🌟T:底层存储的数据类型
🌟Compare:比较方式,默认按照小于方式比较
🌟Alloc:set中元素空间的管理方式,使用STL提供的空间配置器管理
set文档介绍
- set是按照一定次序存储元素的容器
- 在set中,元素的value也标识它(value就是key,类型为T),并且每个value必须是唯一的。set中的元素不能在容器中修改(元素总是const),但是可以从容器中插入或删除它们。
- 在内部,set中的元素总是按照其内部比较对象(类型比较)所指示的特定严格弱排序准则进行排序。
- set容器通过key访问单个元素的速度通常比unordered_set容器慢,但它们允许根据顺序对子集进行直接迭代。
- set在底层是用二叉搜索树(红黑树)实现的。
看一下要点:
⭐️⭐️ set底层是一颗二叉树,内容可以删除,但不允许修改,查找某个元素时间复杂度为logN。
⭐️⭐️ set中不允许出现重复元素,本质是排序+去重
⭐️⭐️ set的迭代器遍历是一个有序序列
⭐️⭐️ set存的是一个值value,但是在底层也是一个键值对,不过这个键值对两个值都相同,即<value,value>,我们在使用时,只需要插入就可以,不需要构建键值对。
⭐️⭐️set比较默认按照小于比较
⭐️⭐️对于unique算法,去重相邻重复元素,去重之前需要先排序,才可以达到去重的效果。并不会改变容器的大小,随后调用erase删除这些重复元素。
nums.erase(unique(nums.begin(), nums.end()), nums.end());
2.set成员函数
1.构造函数
🌟🌟空的set
set< int >s;
🌟🌟迭代器区间构造set
set< int >s(s2.begin(),s2.end());
🌟🌟拷贝构造set
set< int >s(s2);
2.迭代器
遍历
🌟🌟迭代器遍历
set<int>s;
int a[] = { 8, 3, 1,1,2,3,4,5,6,7,10, 6, 4, 7, 14, 13 };
for (auto& e : a)
{s.insert(e);
}
//迭代器遍历
set<int>::iterator it = s.begin();
while (it != s.end())
{cout << *it << " ";it++;
}
cout << endl;
我们可以看到确实完成了去重任务。
🌟🌟范围for
for (auto& e : s){cout << e << " ";}
3.容量
🌟 🌟 empty
判断set是否为空
🌟 🌟 size
找出set中有效元素的个数。
4.修改
🌟 🌟insert
第一个:直接插入一个值
第二个:在某个位置插入一个值
第三个:插入一个迭代器区间
看起来很简单蛮,我们来看点诡异的,第一个的返回值
pair<iterator,bool> 返回一个键值对??
我们看一下文档的介绍
如果插入成功就返回插入结点的迭代器,并且返回true。
如果插入失败,也就是set中有这个元素,返回set中这个元素的迭代器,返回false。
🌟 🌟erase
第一个删除某个迭代器位置的值
第二个删除这个值,注意这个返回值,返回set中val这个元素的个数。
第三个删除一段迭代器区间
第一个删除和第二个删除有什么不同呢??
看一下这段代码,运行结果是什么??
set<int>s;//空的setint a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };for (auto& e : a){s.insert(e);}for (auto& e : s){cout << e << " ";}cout << endl;//查找删除set<int>::iterator pos = s.find(3);s.erase(pos);for (auto& e : s){cout << e << " ";}cout << endl;
成功的把3删除了,那我们如果删除一个不存在的值呢???
pos = s.find(30);s.erase(pos);for (auto& e : s){cout << e << " ";}cout << endl;
系统直接崩溃了,所以我们需要加判断!!!
那如果用第二种方法直接删除一个不存在的值呢??
s.erase(20);
我们发现并没有报错。
对于直接删除:在就删除,不在不做任何处理
我们要注意这两个的区别!!!!
🌟 🌟swap
交换两个set的值
🌟 🌟clear
清空set中的元素
5.其他
🌟 🌟find
查找某个值,如果存在返回对应的迭代器区间,不存在返回end的迭代器。
🌟 🌟count
记录val这个值的个数,在set中出现了多少次。返回set中值为x的元素的个数
🌟 🌟lower_bound
记录大于等于val的迭代器
如果这个值存在,返回这个值的迭代器。
如果这个值不存在,返回大于这个值的迭代器。
在set中存在3,解引用就返回3。查找9,9不存在,就返回下一个
🌟 🌟upper_bound
upper_bound也是同样的道理,但是不同的是,这个返回大于val的迭代器。
三.multiset
multiset本质和set没有太大差别
我们先来看一下文档
- multiset是按照特定顺序存储元素的容器,其中元素是可以重复的。
- 在multiset中,元素的value也会识别它(因为multiset中本身存储的就是<value, value>组成
的键值对,因此value本身就是key,key就是value,类型为T). multiset元素的值不能在容器
中进行修改(因为元素总是const的),但可以从容器中插入或删除。 - 在内部,multiset中的元素总是按照其内部比较规则(类型比较)所指示的特定严格弱排序准则
进行排序。 - multiset容器通过key访问单个元素的速度通常比unordered_multiset容器慢,但当使用迭
代器遍历时会得到一个有序序列。 - multiset底层结构为二叉搜索树(红黑树)
与set相比,不同的是,multiset允许重复的值存在,本质就是排序。其他的都与set相同。
set<int>s;int a[] = { 8, 3, 1,1,2,3,4,5,6,7,10, 6, 4, 7, 14, 13 };for (auto& e : a){s.insert(e);}set<int>::iterator it = s.begin();while (it != s.end()){cout << *it << " ";it++;}cout << endl;for (auto& e : s){cout << e << " ";}
看一下运行结果
我们再来看一下一个的返回值
这个地方就有很大的意义了。
count的意义也就发挥出来了。
四.map
map就是对应我们二叉树中的KV模型
1.map文档介绍
看一下模板参数
包括四个值:
🌟Key:键值对中的key值
🌟T:键值对中的value值
🌟Compare:比较器类型,一般是按照小于比较。如果是自定义类型,需要自己传递这个比较方式,达到要求。
🌟Alloc:通过空间配置器来申请底层空间,不需要用户传递,除非用户不想使用标准库提供的
空间配置器
set文档介绍
- map是关联容器,它按照特定的次序(按照key来比较)存储由键值key和值value组合而成的元素。
- 在map中,键值key通常用于排序和惟一地标识元素,而值value中存储与此键值key关联的内容。键值key和值value的类型可能不同,并且在map的内部,key与value通过成员类型value_type绑定在一起,为其取别名称为pair:
typedef pair<const key, T> value_type; - 在内部,map中的元素总是按照键值key进行比较排序的。
- map中通过键值访问单个元素的速度通常比unordered_map容器慢,但map允许根据顺序对元素进行直接迭代(即对map中的元素进行迭代时,可以得到一个有序的序列)。
- map支持下标访问符,即在[]中放入key,就可以找到与key对应的value。
- map通常被实现为二叉搜索树(更准确的说:平衡二叉搜索树(红黑树))
看一下要点:
🌟🌟map中存储的是一个键值对,键值对中第一个元素,也就是key,根据key值进行排序,确定唯一元素。
第二个值value,与key的内容关联。
🌟🌟元素不可以修改key的值,但是可以修改value的值。
🌟🌟支持下标访问,把key放在[ ]中,可以找到与之对应的value元素。
2.map成员函数
这其中与很多和set的成员函数相同,在这里我就不再重复阐述了。
接下来主要讲述一些不同点,以及重点内容。
1.构造
🌟🌟1.有名构造
pair<string, string>p = { “pear”, “梨” };
m.insert( p);
🌟🌟2.匿名构造
m.insert(pair<string, string>(“apple”, “苹果”));
🌟🌟3.make_pair
m.insert(make_pair(“banana”, “香蕉”));
c++98新增的,本质就是一个函数模板
🌟🌟4.c++11多参数隐式类型转换
m.insert({“strawberrier” ,“草莓”});
2.insert插入
我们看一下官方文档
我们主要看一下第一条:
插入的是一个pair类型,返回值也是一个pair类型
我们来看一下有关返回值的介绍
如果插入到元素存在,返回值中的iterator就指向这个存在的位置的迭代器,bool值为false
如果插入元素不存在,返回值中的iterator就指向这个新插入的位置的迭代器,bool值为true
这里的元素插入是看key的值,与value无关。
3.count
count返回key为x的键值在map中的个数,注意map中key是唯一的,因此该函数的返回值要么为0,要么为1,因此也可以用该函数来检测一个key是否在map中。
4.迭代器
我们注意一下map有两个值,我们再遍历的时候,不能只遍历一个,要把两个分开。
for (auto& kv : m){cout << kv.first << ":" << kv.second << endl;}cout << endl;
first代表pair第一个值,second代表pair第二个值。
5.【】和at
map中支持【】访问元素。
如果我们想要统计水果出现的次数
第一步:先看这个水果在不在!!
第二步:如果不在,就把这一个键值对插入进去。如果在,就让vaule这个值加加。
最后遍历一边元素,就完成了
我们再map中不用折磨麻烦,我们看一下这个巧妙的代码。
// 统计水果出现的次数
string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };
map<string, int> m;
for (auto& e : arr)
{m[e]++;
}
m[e]++;就完成了操作。
我们来看一下怎末实现的??
【】的返回值是value类型,参数是一个key类型。
在文档中还有这样一段话,我们来剖析一下。
这个【】需要调用insert,insert上面我们已经介绍过了,拆分来看一下
🌟insert(make_pair(k,mapped_type()))这个是insert的原型,插入一个键值对,key值必须传入,如果value不传入,采用缺省值。
这个k存在,就返回这个k这个值的迭代器,不存在,就返回新插入这个k的迭代器。
🌟this->insert(make_pair(k,mapped_type()))调用这个返回值pair
🌟(this->insert(make_pair(k,mapped_type()))).first调用这个返回值的pair的第一个元素iterator,迭代器本身就是指针。
🌟(*((this->insert(make_pair(k,mapped_type()))).first)).second
这个迭代器指向的第二个元素,就是value.
通过这种方式我们就拿到了value的值
如果不存在,插入成功,返回新插入元素的迭代器
如果已存在,插入失败,返回该key所在位置的迭代器
operator[]函数最后将insert返回值键值对中的value返回
在元素访问时,有一个与operator[]类似的操作at()(该函数不常用)函数,都是通过key找到与key对应的value然后返回其引用
不同的是:当key不存在时,operator[]用默认value与key构造键值对然后插入,返回该默认value,at()函数直接抛异常。
我们看一下这段代码的结果是什么??
map<string, string> dict;dict.insert(make_pair("sort", "排序"));dict.insert(make_pair("string", "字符串"));dict.insert(make_pair("sort", "xxx"));
只与key的值有关,与value无关。
key相同,value不同,不会插入也不会更新
这个【】就有很多功能了,也可以插入,也可以查找,也可以修改
五.multimap
map和multimap没有太大区别
- Multimaps是关联式容器,它按照特定的顺序,存储由key和value映射成的键值对<key,value>,其中多个键值对之间的key是可以重复的。
- 在multimap中,通常按照key排序和惟一地标识元素,而映射的value存储与key关联的内容。key和value的类型可能不同,通过multimap内部的成员类型value_type组合在一起,value_type是组合key和value的键值对:
typedef pair<const Key, T> value_type; - 在内部,multimap中的元素总是通过其内部比较对象,按照指定的特定严格弱排序标准对key进行排序的。
- multimap通过key访问单个元素的速度通常unordered_multimap容器慢,但是使用迭代器直接遍历multimap中的元素可以得到关于key有序的序列。
- multimap在底层用二叉搜索树(红黑树)来实现
multimap和map的唯一不同就是:map中的key是唯一的,而multimap中key是可以重复的。
multimap中没有重载operator[]操作
总结
以上就是今天要讲的内容,本文仅仅详细介绍了C++map和set的相关内容。希望对大家的学习有所帮助,仅供参考 如有错误请大佬指点我会尽快去改正 欢迎大家来评论~~ 😘 😘 😘