目录

关联式容器
| 特性 | 序列式容器 vector/list | 关联式容器 map/set |
|---|---|---|
| 顺序 | 插入顺序 | key 排序 |
| 结构 | 线性排列 | 红黑树 |
| 查找 | O (n) 慢 | O (log n) 快 |
| 元素 | 单个值 | key/key+value |
| 排序 | 不会自动排 | 自动排序 |
| 用途 | 存数据、遍历 | 查找、映射、去重 |
序列式容器:map/set
STL总共实现了两种不同结构的管理式容器:树形结构和哈希结构。树形结构的关联式容器主要有四种:map、set、multimap、multiset 。共同点都是用平衡搜索树,即红黑树作为底层结构,容器中的元素是一个有序的序列。
键值对
用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量key和value,key代表键值,value代表与key对应的信息。如英文单词和其中文含义就是一个一一对应的关系,就可以把英文单词当做key,中文含义当做value。
cpp
//SGI 版 STL 中的实现
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) {}
};
pair中的first表示键值,second表示实值,即一一对应的关系。
构造一个匿名键值对pair对象,可以如下表示:
cpp
pair<string, string>{"left","左边"};
库里面写了一个函数模板:make_pair ,作用就是:自动帮你创建一个 pair 对象。
如:
cpp
make_pair("right","右边");
map -> key/value搜索树
对map的使用
cpp
void Testkeyvalue3()//使用map
{
map<string, string> dict;
pair<string, string> kv1("hello","你好" );
dict.insert(kv1);//insert插入的参数就是pair键值
dict.insert(pair<string, string>{"left","左边"});//匿名对象
dict.insert(make_pair("right","右边"));//使用make_pair(),实际是函数模板,即可不用指定传参类型
//pair<string, string> kv2 = { "apple","苹果" }; 多参数的构造函数支持隐式类型转换
//dict.insert(kv2);这两行就分别是构造+拷贝构造
dict.insert({ "apple","苹果" });//构造和拷贝构造优化为直接构造
//会按key的大小进行插入
//对map的遍历输出
//auto it1 = dict.begin();
//while (it1 != dict.end())
//{
// //cout << (*it1).first << "->" << (*it1).second << endl;
// cout << it1->first << "->" << it1->second << endl;//常用
// it1++;
//}
string str;
while (cin >> str)
{
auto s = dict.find(str);
if (s != dict.end())//不等于相当于找到了str的位置,find找不到时返回的就是end迭代器
{
cout << str << "->" << s->second << endl;
}
else
{
cout << "无此单词,请重新输入" << endl;
}
}
}
迭代器重载了 * 和-> 运算符 ,所以可以直接当成指针来使用。
(*it1).first:*it1是对迭代器的解引用,
● .first:访问这个键值对的键
● .second:访问这个键值对的值
it1->first:-> 是指针访问成员运算符,it1->返回的是operator->,即pair *
相当于:(it1.operator->())->first
operator\[\]
形式:
cpp
mapped_type& operator[] (const key_type& k);//即参数值是key,返回值是value

\[\]的功能等价于:
解释一下:
先看insert这块:
mapped_type(),一个缺省值,不写的话相当于调用的是默认构造
insert的格式:
它的返回值是个pair,分为两种情况:
- 当map中要插入一个
新的key时,此时返回值就是<新插入元素的迭代器,true> - 当map中
已经存在一个key时,此时返回值就是<map中该key的迭代器,false>
insert返回的是pair,然后取first,即迭代器,进行解引用,即得到元素的<key,value>,再取second,所以\[\]的返回值就是value。
使用\[\]用于查找时要注意它是否存在,若它原本不存在则会变为插入。
multimap
它的key值可以重复,而map的key不能重复。
multimap中允许出现多个重复的键值,这就意味着operator[]无法确认调用者的意图,也就不知道要返回哪个 键值 对应的 实值。
所以multimap中没有提供 operator[]
set -> key搜索树
set的简单介绍
- set是按照一定次序存储元素的容器。
- set中的元素
不能在容器中修改,元素总是const类型,但是可以从容器中插入或删除它们。 - set中的元素总是会按照其内部类型所指示的特定严格弱排序准则进行排序。如对int类型就是升序,对string类型就是按字典序排序。
- set中只存放value,但在底层实际存放的是由<value, value>构成的键值对。
- set中查找某个元素的时间复杂度为O(log n)
迭代器遍历默认走的是中序遍历,会自动去重。
set的使用
对于set的各种函数,直接看文档即可:set的文档
简单说一下关于删除的:
删除掉最小值:s.erase(s.begin());
cpp
for (int i = 1; i < 10; i++) myset.insert(i * 10); // 10 20 30 40 50 60 70 80 90
// [30, 60]
// >= 30
itlow = myset.lower_bound(30); //
// > 60
itup = myset.upper_bound(60); //
myset.erase(itlow, itup); // 10 20 70 80 90
关于set的使用也很简单:
cpp
//前面要加上set的头文件
#include<iostream>
#include<set>
using namespace std;
int main()
{
int arr[] = {1, 3, 2, 6, 7, 3, 0, 8, 7, 3, 1, 1};
set<int> si(arr, arr+sizeof(arr)/sizeof(arr[0]));
cout << si.size() << endl;
//打印set中的元素
for(auto& e : si)
cout << e << " ";
//结果为:0 1 2 3 6 7 8,发现对其去了重,并进行了排序。
return 0;
}
multiset
multiset不会去重,在删除时,会把所有相同的值都删除。
multiset和set的区别:
- find查找x时,多个x在树中,返回中序第一个x
- 插入时,允许相等的值插入
相关OJ题
以下几个题都可以用set或map实现。
138. 随机链表的复制 - 力扣(LeetCode)
- 处理深拷贝
- 利用map构造原节点和拷贝节点之间的关系
问题点:新旧节点random的映射还有点懵逼。即对\[\]的使用还不熟练。
法一:把两个数组都放入set中,小的++,相等的同时++;
法二:排序+去重,依次比较,小的++,相等的同时++;
应用:差集进行同步,交集进行比对
坑点:map对key进行排过序了,但是我如果直接用sort对value即次数进行排序,那就会乱,因为sort底层是快排,不稳定,可以用stable_sort,也可以调整仿函数的比较逻辑
感谢大家观看,如果大家喜欢,希望大家一键三连支持一下,如有表述不正确,也欢迎大家批评指正。
