1. 序列式容器和关联式容器
前面我们接触过的STL中的部分容器如:string、vector、list、deque、array、forward_list等,这些容器统称为序列式容器,因为逻辑结构为线性序列的数据结构,两个位置存储的值之间一般没有紧密的关联关系,比如交换一下,他依旧是序列式容器。顺序容器中的元素是按它们在容器中的存储位置来顺序保存和访问的。
关联式容器 也是用来存储数据的,与序列式容器不同的是,关联式容器逻辑结构通常是非线性结构,两个位置有紧密的关联关系,交换一下,他的存储结构就被破坏了。顺序容器中的元素是按关键字来保存和访问的。关联式容器有map/set系列和unordered_map/unordered_set系列。
set和map底层是红黑树,红黑树是一颗平衡二叉搜索树。set是key搜索场景的结构。map是key/value搜索场景的结构。
2. set系列的使用
2.1 set 和 multiset参考文档
https://legacy.cplusplus.com/reference/set/
2.2 set类的介绍

set的声明如下,T就是set底层关键字的类型
- set默认要求T支持小于比较,如果不支持或者想按自己的需求走可以自行实现仿函数传给第二个模板参数
- set底层存储数据的内存是从空间配置器申请的,如果需要可以自己实现内存池,传给第三个参数
- 一般情况下,我们都不需要传后两个模板参数
- set底层是用红黑树实现,增删查效率是
,迭代器遍历走的是搜索树的中序,所以是有序的。 - 前面的已经了解过vector/list等容器的使用,下面的便是set相关的比较重要的接口:
2.3 set的构造和迭代器
set的构造关注以下的接口即可。
cpp
// empty (1) ⽆参默认构造
explicit set(const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type());
// range (2) 迭代器区间构造
template <class InputIterator>
set(InputIterator first, InputIterator last,
const key_compare& comp = key_compare(),
const allocator_type & = allocator_type());
// copy (3) 拷⻉构造
set(const set& x);
// initializer list (5) initializer 列表构造
set(initializer_list<value_type> il,
const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type());
// 迭代器是⼀个双向迭代器
iterator->a bidirectional iterator to const value_type
// 正向迭代器
iterator begin();
iterator end();
// 反向迭代器
reverse_iterator rbegin();
reverse_iterator rend();

set支持正向和反向迭代遍历(双向迭代器),遍历默认按升序顺序,因为底层是二叉搜索树,迭代器遍历走的中序;支持迭代器就意味着支持范围for,set的iterator和const_iterator都不支持迭代器修改数据,修改关键字数据,破坏了底层搜索树的结构。

cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<list>
#include<set>
using namespace std;
int main()
{
//去重+升序排序
set<int> s;
//去重+降序
//set<int, greater<int>> s;
s.insert(5);
s.insert(2);
s.insert(7);
s.insert(5);
//set<int>::iterator it = s.begin();
auto it = s.begin();
while (it != s.end())
{
//*it = 1; //迭代器指向的是key,key是不能修改的
cout << *it << " ";
++it;
}
cout << endl;
return 0;
}
运行结果:

cpp
int main()
{
//去重+升序排序
set<int> s = {5,6,9,1,3,4,6};
//去重+降序
//set<int, greater<int>> s;
s.insert(5);
s.insert(2);
s.insert(7);
s.insert(5);
//set<int>::iterator it = s.begin();
auto it = s.begin();
while (it != s.end())
{
//*it = 1; //迭代器指向的是key,key是不能修改的
cout << *it << " ";
++it;
}
cout << endl;
return 0;
}
运行结果:(重复插入的也只会插入一次,例如:5只有一个)

2.4 set的增删查
set的增删查关注以下几个接口即可:
cpp
Member types
key_type->The first template parameter(T)
value_type->The first template parameter(T)
// 单个数据插⼊,如果已经存在则插⼊失败
pair<iterator, bool> insert(const value_type& val);
// 列表插⼊,已经在容器中存在的值不会插⼊
void insert(initializer_list<value_type> il);
// 迭代器区间插⼊,已经在容器中存在的值不会插⼊
template <class InputIterator>
void insert(InputIterator first, InputIterator last);
// 查找val,返回val所在的迭代器,没有找到返回end()
iterator find(const value_type& val);
// 查找val,返回Val的个数
size_type count(const value_type& val) const;
// 删除⼀个迭代器位置的值
iterator erase(const_iterator position);
// 删除val,val不存在返回0,存在返回1
size_type erase(const value_type& val);
// 删除⼀段迭代器区间的值
iterator erase(const_iterator first, const_iterator last);
// 返回⼤于等val位置的迭代器
iterator lower_bound(const value_type& val) const;
// 返回⼤于val位置的迭代器
iterator upper_bound(const value_type& val) const;
2.5 insert 和 迭代器遍历使用样例

cpp
int main()
{
//去重+升序排序
set<int> s = {5,6,9,1,3,4,6};
//去重+降序
//set<int, greater<int>> s;
s.insert(5);
s.insert(2);
s.insert(7);
s.insert(5);
//......
//插入一段initializer_list列表值,已经存在的值插入失败
s.insert({ 2,9,3,15 });
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
return 0;
}
运行结果:

string也是支持比较key的,只要能够比较大小就可以支持
2.6 find 和 erase 使用样例

算法库里面的find和这里的find有区别吗?

算法库里面的find就是一个暴力查找,是一个单向迭代器




cpp
int main()
{
set<int> s = { 4,2,7,1,5,9,7,8,5 };
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
//删除最小值
s.erase(s.begin());
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
return 0;
}
运行结果:

cpp
int main()
{
set<int> s = { 4,2,7,1,5,9,7,8,5 };
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
//删除最小值
s.erase(s.begin());
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
//直接删除x
int x;
cin >> x;
int num = s.erase(x); //返回值为整型是要和multi版本结合起来
if (num == 0)
{
cout << x << "不存在!" << endl;
}
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
return 0;
}
运行结果:

cpp
//......
// 直接查找在利用迭代器删除x
// 利用迭代器去删除,没有去删的话就会报错,必须是一个有效的迭代器
cin >> x;
auto pos = s.find(x);
if (pos != s.end())
{
s.erase(pos);
}
else
{
cout << x << "不存在" << endl;
}
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
2.7 count:返回key有几个

对于set而言,不是1就是0。count返回类型是size_type,跟multi版本配合。
用 count 来判断这个值在不在:
cpp
int main()
{
set<int> s = { 4,2,7,1,5,9,7,8,5 };
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
int x = 0;
cin >> x;
if (s.count(x))
{
cout << x << "在!" << endl;
}
else
{
cout << x << "不在!" << endl;
}
return 0;
}
运行结果:


multi版本如何实现count,先调用find,如果找到中序的第一个之后,++下一个,是,++下一个,不是,那么就有两个3。相等的值走中序一定是挨着的。

最常用的还是迭代器、insert、erase、find。
erase还有删除一段区间的功能,但是提供的是左闭右开的区间


cpp
int main()
{
set<int> myset = { 10,20 ,30, 40, 50, 60, 70, 80, 90 };
for (auto e : myset)
{
cout << e << " ";
}
cout << endl;
//实现查找到的[itlow,itup)包含[30,60]区间
//返回 >= 30
auto itlow = myset.lower_bound(30);
//返回 >60
auto itup = myset.upper_bound(60);
//删除这段区间的值
myset.erase(itlow, itup);
for (auto e : myset)
{
cout << e << " ";
}
cout << endl;
return 0;
}
运行结果:

cpp
set<int> myset = { 10,20 ,35, 40, 50, 65, 70, 80, 90 };
运行结果:


在set没啥用,或者用处很小
2.8 mulitset 和 set 的差异
multiset 和 set 的使用基本完全类似,主要的区别在于mulitset支持值冗余,那么insert/find/count/erase都围绕着支持值冗余有所差异,具体参看下面的代码:
cpp
int main()
{
multiset<int> s = { 4,6,3,9,6,2,7,6,3 };
auto it = s.begin();
while (it != s.end())
{
cout << *it << " ";
++it;
}
cout << endl;
return 0;
}
运行结果:

set 不是排序,而是排序+去重;multiset 才是排序
find 查找:
cpp
int main()
{
multiset<int> s = { 4,6,3,9,6,2,7,6,3 };
auto it = s.begin();
while (it != s.end())
{
cout << *it << " ";
++it;
}
cout << endl;
//find
//相比set不同的是,x可能会存在多个,find查找中序的第一个
int x;
cin >> x;
auto pos = s.find(x);
while (pos != s.end() && *pos == x)
{
cout << *pos << " ";
++pos;
}
cout << endl;
return 0;
}
运行结果:

count 计数:
cpp
int main()
{
multiset<int> s = { 4,6,3,9,6,2,7,6,3 };
auto it = s.begin();
while (it != s.end())
{
cout << *it << " ";
++it;
}
cout << endl;
//find
//相比set不同的是,x可能会存在多个,find查找中序的第一个
int x;
cin >> x;
auto pos = s.find(x);
while (pos != s.end() && *pos == x)
{
cout << *pos << " ";
++pos;
}
cout << endl;
////相比set不同的是,count会返回x的实际个数
cout << s.count(x) << endl;
return 0;
}
运行结果:(3个6)

erase 删除:
cpp
int main()
{
multiset<int> s = { 4,6,3,9,6,2,7,6,3 };
auto it = s.begin();
while (it != s.end())
{
cout << *it << " ";
++it;
}
cout << endl;
//find
//相比set不同的是,x可能会存在多个,find查找中序的第一个
int x;
cin >> x;
auto pos = s.find(x);
while (pos != s.end() && *pos == x)
{
cout << *pos << " ";
++pos;
}
cout << endl;
////相比set不同的是,count会返回x的实际个数
cout << s.count(x) << endl;
////相比set不同的是,erase给值时会删除所有的x
s.erase(x);
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
return 0;
}
运行结果:(删除3个6)

2.9 两个数组的交集
同时我的文章里面也有这道题的解法
2.10 环形链表
数据结构初阶阶段,我们通过证明一个指针从头开始走一个指针从相遇点开始走,会在入口点相遇,理解证明都会很麻烦。这里使用set的话,就会变得非常简单方便。
考虑用set,将结点的指针放进去,遍历这个链表,此链表不带环,就会走到空。带环的话,当前的结点已经在set里面说明带环,并且此节点就是环的入口点。唯一的缺点就是有O(N)的空间复杂度
cpp
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
set<ListNode*> s;
ListNode* cur = head;
while(cur)
{
if(s.count(cur)) //存在,返回非0的值即为真
return cur;
else //否则不存在,为假,则插入
{
s.insert(cur);
}
cur = cur->next;
}
return nullptr;
}
};
还有一种写法:
cpp
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
set<ListNode*> s;
ListNode* cur = head;
while(cur)
{
auto ret = s.insert(cur);
if(ret.second == false)
return cur;
cur = cur->next;
}
return nullptr;
}
};
insert 的返回类型是pair:

3. map系列的使用
3.1 map 和 mulitmap 参考文档
https://legacy.cplusplus.com/reference/map/
3.2 map 类的介绍
map是key/value的模型,一个节点存在两个值,但是不想之前定义的一个key,一个value分开的;而是将这两个存在一个结构中去,而这个结构就叫做 pair。
map的声明如下,Key就是map底层关键字的类型,T是map底层value的类型,set默认支持小于比较,如果不支持或者需要的话可以自行实现仿函数传给第二个模板参数,map底层存储数据的内存是从空间配置器申请的。一般情况下,我们都不需要传后两个模板参数。map底层使用红黑树实现,增删查改效率是
,迭代器遍历是走的中序,所以是按key有序顺序遍历的。(跟value无关)

3.3 pair类型介绍
map底层的红黑树结点中的数据,使用 pair<Key,T>存储键值对数据。
保护Key不能被修改。
cpp
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)
{
}
template<class U, class V>
pair(const pair<U, V>& pr) : first(pr.first), second(pr.second)
{
}
};
template <class T1, class T2>
inline pair<T1, T2> make_pair(T1 x, T2 y)
{
return (pair<T1, T2>(x, y));
}
3.4 map的构造

map的构造与以下的接口有关。
cpp
// empty (1) ⽆参默认构造
explicit map(const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type());
// range (2) 迭代器区间构造
template <class InputIterator>
map(InputIterator first, InputIterator last,
const key_compare& comp = key_compare(),
const allocator_type & = allocator_type());
// copy (3) 拷⻉构造
map(const map& x);
// initializer list (5) initializer 列表构造
map(initializer_list<value_type> il,
const key_compare & comp = key_compare(),
const allocator_type & alloc = allocator_type());
// 迭代器是⼀个双向迭代器
iterator->a bidirectional iterator to const value_type
// 正向迭代器
iterator begin();
iterator end();
// 反向迭代器
reverse_iterator rbegin();
reverse_iterator rend()
map支持正向和反向迭代遍历(双向迭代器),遍历默认按key的升序顺序,因为底层是二叉搜索树,迭代器遍历走的中序;支持迭代器就意味着支持范围for,map支持修改value数据,不支持修改key数据,修改关键字数据,破坏底层搜索树的结构。
3.5 map的增删查
map的增删查与以下的接口有关。
cpp
Member types
key_type -> The first template parameter (Key)
mapped_type -> The second template parameter (T)
value_type -> pair<const key_type,mapped_type>
// 单个数据插⼊,如果已经key存在则插⼊失败,key存在相等value不相等也会插⼊失败
pair<iterator,bool> insert (const value_type& val);
// 列表插⼊,已经在容器中存在的值不会插⼊
void insert (initializer_list<value_type> il);
// 迭代器区间插⼊,已经在容器中存在的值不会插⼊
template <class InputIterator>
void insert (InputIterator first, InputIterator last);
// 查找k,返回k所在的迭代器,没有找到返回end()
iterator find (const key_type& k);
// 查找k,返回k的个数
size_type count (const key_type& k) const;
// 删除⼀个迭代器位置的值
iterator erase (const_iterator position);
// 删除k,k存在返回0,存在返回1
size_type erase (const key_type& k);
// 删除⼀段迭代器区间的值
iterator erase (const_iterator first, const_iterator last);
// 返回⼤于等k位置的迭代器
iterator lower_bound (const key_type& k);
// 返回⼤于k位置的迭代器
const_iterator lower_bound (const key_type& k) const;
map增接口,插入的pair键值对数据,跟set有所不同,但是查和删的接口只用关键字key跟set是完全类似的,不过find返回iterator,不仅仅可以确认key在不在,还找到key映射的value,同时通过迭代还可以修改value。
insert:


cpp
int main()
{
//C++ 98
map<string, string> dict;
//构造一个pair,插入有名对象
pair<string, string> kv1("sort", "排序");
dict.insert(kv1);
//插入匿名对象
dict.insert(pair<string, string>("string", "字符串"));
//make_pair ------ 是一个函数模板,自动推导类型
dict.insert(make_pair("left", "左边")); //推荐
//C++ 11
dict.insert({ "right", "右边" }); // 推荐 单参数构造函数支持隐式类型转换(C++98支持),多参数构造函数也支持隐式类型转换(C++11支持)
return 0;
}
迭代器:
cpp
#include<map>
int main()
{
//C++ 98
map<string, string> dict;
//构造一个pair,插入有名对象
pair<string, string> kv1("sort", "排序");
dict.insert(kv1);
//插入匿名对象
dict.insert(pair<string, string>("string", "字符串"));
//make_pair ------ 是一个函数模板,自动推导类型
dict.insert(make_pair("left", "左边")); //推荐
//C++ 11
dict.insert({ "right", "右边" }); //单参数构造函数支持隐式类型转换(C++98支持),多参数构造函数也支持隐式类型转换(C++11支持)
//迭代器
//map<string, string>::iterator it = dict.begin();
auto it = dict.begin();
while (it != dict.end())
{
//cout << *it <<endl; //报错:没有与这些操作数匹配的 "<<" 运算符,因为pair是没有支持流插入,流提取的
cout << (*it).first << " " << (*it).second << endl;
cout << it->first << " " << it->second << endl;
//迭代器会重载两个运算符,operator * 模拟指针的行为解引用,operator ->模拟的是结构的指针
//所以map里面不存两个值,是因为解引用的时候不知道该怎么返回,解引用的时候不能返回两个值,所以存成pair结构,方便迭代器访问
cout << it.operator->()->first << " " << it->second << endl;
//第一个 -> 是运算符重载 ,返回里面数据的指针,所以返回的是 pair*,pair*再加一个 -> 就访问成员了
++it;
}
cout << endl;
return 0;
}
运行结果:

说明:
- cout << *it <<endl;
//报错:没有与这些操作数匹配的 "<<" 运算符,因为pair是没有支持流插入,流提取的。正确写法:cout << (*it).first << " " << (*it).second << endl;
- cout << it->first << " " << it->second << endl;
//迭代器会重载两个运算符,operator * 模拟指针的行为解引用,operator ->模拟的是结构的指针
//所以map里面不存两个值,是因为解引用的时候不知道该怎么返回,解引用的时候不能返回两个值,所以存成pair结构,方便迭代器访问
- cout << it.operator->()->first << " " << it->second << endl;
//第一个 -> 是运算符重载 ,返回里面数据的指针,所以返回的是 pair*,pair*再加一个 -> 就访问成员了
//为了可读性省略了一个箭头
initializer_list 初始化:
//initializer_list 构造及迭代遍历
- map<string, string> dict = { {"left","左边"},{"right","右边"}, {"string","字符串"}, {"top","上边"}, {"erase","删除"} };
// 最外层的{}是initializer_list的,同类型构成的数组
// {"left","左边"}去隐式类型转换为pair
cpp
int main()
{
map<string, string> dict = { {"left","左边"},{"right","右边"}, {"string","字符串"}, {"top","上边"}, {"erase","删除"} };
for (const auto& kv : dict) //不想改变 加上const,想改变的话就算了
{
cout << kv.first <<" "<< kv.second << endl;
}
return 0;
}
key相同不会再插入,value不同也不会再插入。
initializer_list 拿到这些值去遍历,也是调用insert,严格来说是insert的功能。
运行结果:

cpp
int main()
{
//initializer_list 构造及迭代遍历
map<string, string> dict = { {"left","左边"},{"right","右边"}, {"string","字符串"}, {"top","上边"}, {"erase","删除"} };
dict.insert({ "left","112233" }); //多参数走隐式类型转化
for (const auto& kv : dict) //不想改变 加上const,想改变的话就算了
{
cout << kv.first <<" "<< kv.second << endl;
}
return 0;
}
运行结果:(并没有插入已经存在的key值,也没有改变value值)

erase:(查找和删除只给key就行了)

3.6 map的数据修改
前面提到map支持修改mapped_type 数据,不支持修改key数据,修改关键字数据,破坏了底层搜索树的结构。
map第一个支持修改的方式是通过迭代器,迭代器遍历时或者find返回key所在的iterator修改,map还有一个非常重要的修改接口 operator[ ] ,但是operator[ ]不仅仅支持修改,还支持插入数据和查找数据,所以他是一个多功能复合接口
需要注意从内部实现角度,map这里把我们传统说的value值,给的是T类型,typedef为mapped_type。而value_type是红黑树结点中存储的pair键值对值。日常使用还是习惯将这里的T映射值叫做value。
cpp
Member types
key_type -> The first template parameter (Key)
mapped_type -> The second template parameter (T)
value_type -> pair<const key_type,mapped_type>
// 查找k,返回k所在的迭代器,没有找到返回end(),如果找到了通过iterator可以修改key对应的mapped_type值
iterator find (const key_type& k);
// ⽂档中对insert返回值的说明
// The single element versions (1) return a pair, with its member pair::first set to an iterator pointing to either the newly inserted element or to the element with an equivalent key in the map. The pair::second element in the pair is set to true if a new element was inserted or false if an equivalent key already existed.
// insert插⼊⼀个pair<key, T>对象
// 1、如果key已经在map中,插⼊失败,则返回⼀个pair<iterator,bool>对象,返回pair对象first是key所在结点的迭代器,second是false
// 2、如果key不在在map中,插⼊成功,则返回⼀个pair<iterator,bool>对象,返回pair对象first是新插⼊key所在结点的迭代器,second是true
// 也就是说⽆论插⼊成功还是失败,返回pair<iterator,bool>对象的first都会指向key所在的迭代器
// 那么也就意味着insert插⼊失败时充当了查找的功能,正是因为这⼀点,insert可以⽤来实现operator[]
// 需要注意的是这⾥有两个pair,不要混淆了,⼀个是map底层红⿊树节点中存的pair<key, T>,另
//⼀个是insert返回值pair<iterator,bool>
pair<iterator,bool> insert (const value_type& val);
mapped_type& operator[] (const key_type& k);
// operator的内部实现
mapped_type& operator[] (const key_type& k)
{
// 1、如果k不在map中,insert会插⼊k和mapped_type默认值,同时[]返回结点中存储mapped_type值//的引⽤,那么我们可以通过引⽤修改返映射值。所以[]具备了插⼊+修改功能
// 2、如果k在map中,insert会插⼊失败,但是insert返回pair对象的first是指向key结点的迭代器,返回值同时[]返回结点中存储mapped_type值的引⽤,所以[]具备了查找+修改的功能
pair<iterator, bool> ret = insert({ k, mapped_type() });
iterator it = ret.first;
return it->second;
}
3.7 构造遍历及增删查使用样例
文章前部分写的有对应的遍历和增删查对应的样例,这里就不再过多举例。
3.8 map的迭代器和 [ ] 功能样例
统计水果的次数:
cpp
int main()
{
string arr[] = { "苹果","西瓜", "柚子", "苹果", "柚子", "苹果", "西瓜", "芭乐" , "柚子","西瓜","柚子","柚子" };
map<string, int> countMap;
for (auto& e : arr)
{
auto it = countMap.find(e);
if (it == countMap.end())
{
countMap.insert({ e,1 }); //插入pair,走隐式类型转换
}
else
{
it->second++;
}
}
for (const auto& kv : countMap)
{
cout << kv.first << " " << kv.second << endl;
}
return 0;
}
运行结果:

还有一种简洁的写法:operator[ ]
cpp
int main()
{
string arr[] = { "苹果","西瓜", "柚子", "苹果", "柚子", "苹果", "西瓜", "芭乐" , "柚子","西瓜","柚子","柚子" };
map<string, int> countMap;
for (auto& e : arr)
{
countMap[e]++;
}
for (const auto& kv : countMap)
{
cout << kv.first << " " << kv.second << endl;
}
return 0;
}
运行结果:

这里的operator[ ]和之前的不一样,给这里的operator[ ]一个key,返回对应key的value的引用。那么返回value的引用意味着还可以修改这个value,既可以查找也可以修改。
operator[ ] 是如何实现的?


mapped_type 就是对应的是value。


调的是insert,operator[ ],不是用find来实现,而是用insert来实现。
insert的返回值(迭代器和布尔)重点理解:


验证代码:
cpp
int main()
{
string arr[] = { "苹果","西瓜", "柚子", "苹果", "柚子", "苹果", "西瓜", "芭乐" , "柚子","西瓜","柚子","柚子" };
map<string, int> countMap;
for (auto& e : arr)
{
//auto it = countMap.find(e);
//if (it == countMap.end())
//{
// countMap.insert({ e,1 }); //插入pair,走隐式类型转换
//}
//else
//{
// it->second++;
//}
countMap[e]++;
}
//iterator在类里面是可以用的,类外面不行(指定类域解决)
pair<map<string, int>::iterator, bool> ret1 = countMap.insert({"波罗蜜",1});
pair<map<string, int>::iterator, bool> ret2 = countMap.insert({"柚子",1});
for (const auto& kv : countMap)
{
cout << kv.first << " " << kv.second << endl;
}
return 0;
}

insert 只能完成插入,不能完成修改。之前已经存在的key,不会更新对应的value。
模拟实现 operator [ ]:大概的一个框架
cpp
V& operator[](const K& key)
{
pair<iterator, bool> ret = insert({ key,V() }); //value给缺省值
//无论插入成功还是失败,iterator指向的是key所在的迭代器
//一个是insert返回值的pair,一个是结点里面的key/value的pair
return ret.first->second;
//ret.first 取pair里面的迭代器,迭代器就可以取到key对应的value
//迭代器里面有个节点指针,节点指针里面有个pair,返回key/value里面的value
//简单来说operator[]返回的是key对应的value,如果没有key,现插一个,value给缺省值
//其次operator[]返回的是value的引用
}
cpp
int main()
{
map<string, string> dict;
//插入(基本不会这样用)
dict["left"];
//插入+修改
dict["right"] = "右边";
//修改
dict["left"] = "左边";
//查找 key 对应的 value
cout << dict["left"] << endl;
return 0;
}
[ ]的作用:插入 和 插入+修改

[ ]的作用:修改

[ ]的作用:查找

用统计水果次数的例子来理解operator[ ] 的实现:
如何用以下一行代码就是实现了统计次数:
cpp
map<string, int> countMap;
for (auto& e : arr)
{
countMap[e]++;
}

map允许修改value,不能修改key。
上面的first,second不是同一个pair:
- first返回的是迭代器和bool的pair的first,也就是迭代器;
- second是这个迭代器指向的结点的key/valie pair的second,也就是value。
3.9 mulitmap 和 map 的差异
multimap和map的使用基本完全类似,主要区别点在于multimap支持关键值key冗余,那么insert/find/count/erase都围绕着支持关键值key冗余有所差异,这里跟set和multimap完全一样,比如find时,有多个key,返回中序第一个。其次就是multimap不支持 [ ],因为支持key冗余,[ ]就只能支持插入了,不能支持修改。
cpp
int main()
{
map<string, string> dict;
dict.insert({ "right", "右边" });
dict.insert({ "left", "左边" });
dict.insert({ "left", "11223" }); //插入失败
multimap<string, string> mdict;
mdict.insert({ "right", "右边" });
mdict.insert({ "left", "左边" });
mdict.insert({ "left", "11223" }); //插入成功
return 0;
}
调试:

multimap 中 key相同,不管value相不相同都进行插入。所以multimap不能用来统计次数。


其次就是multimap不支持 [ ],[ ]返回的是key对应的value,key 有多个,到底是哪一个呢?就不知道了,所以就不提供 [ ]。multimap使用的场景相对少的。
std::multimap::equal_range:用的更少

cpp
int main ()
{
std::multimap<char,int> mymm;
mymm.insert(std::pair<char,int>('a',10));
mymm.insert(std::pair<char,int>('b',20));
mymm.insert(std::pair<char,int>('b',30));
mymm.insert(std::pair<char,int>('b',40));
mymm.insert(std::pair<char,int>('c',50));
mymm.insert(std::pair<char,int>('c',60));
mymm.insert(std::pair<char,int>('d',60));
std::cout << "mymm contains:\n";
for (char ch='a'; ch<='d'; ch++)
{
std::pair <std::multimap<char,int>::iterator, std::multimap<char,int>::iterator> ret;
ret = mymm.equal_range(ch);
std::cout << ch << " =>";
for (std::multimap<char,int>::iterator it=ret.first; it!=ret.second; ++it)
std::cout << ' ' << it->second;
std::cout << '\n';
}
return 0;
}

3.10 随机链表的复制



完整代码:
cpp
/*
// Definition for a Node.
class Node {
public:
int val;
Node* next;
Node* random;
Node(int _val) {
val = _val;
next = NULL;
random = NULL;
}
};
*/
class Solution {
public:
Node* copyRandomList(Node* head) {
map<Node*,Node*> curCopyMap;
Node*copyHead = nullptr, *copyTail = nullptr;
Node* cur = head;
while(cur)
{
if(copyTail == nullptr)
{
copyHead = copyTail = new Node(cur->val);
}
else
{
copyTail->next = new Node(cur->val);
copyTail = copyTail->next;
}
//原结点和拷贝节点 map kv存储
curCopyMap[cur] = copyTail;
cur = cur->next;
}
//处理random
cur = head;
Node* copy = copyHead;
while(cur)
{
if(cur->random == nullptr)
{
copy->random = nullptr;
}
else
{
copy->random = curCopyMap[cur->random];
}
cur = cur->next;
copy = copy->next;
}
return copyHead;
}
};
3.11 前K个高频单词
方法1:

方法2:

完整代码:
cpp
class Solution {
public:
vector<string> topKFrequent(vector<string>& words, int k) {
//统计次数
map<string,int> countMap;
for(auto& str : words)
{
countMap[str]++;
}
multimap<int,string,greater<int>> sortMap;
for(auto& kv : countMap)
{
sortMap.insert({kv.second,kv.first});
}
vector<string> v;
auto it = sortMap.begin();
while(k--)
{
v.push_back(it->second);
++it;
}
return v;
}
};
方法2:
cpp
class Solution {
public:
struct Compare
{
bool operator()(const pair<string, int>& x, const pair<string, int>& y)
const
{
return x.second > y.second || (x.second == y.second && x.first <
y.first);;
}
};
vector<string> topKFrequent(vector<string>& words, int k) {
map<string, int> countMap;
for (auto& e : words)
{
countMap[e]++;
}
vector<pair<string, int>> v(countMap.begin(), countMap.end());
// 仿函数控制降序,仿函数控制次数相等,字典序⼩的在前⾯
sort(v.begin(), v.end(), Compare());
// 取前k个
vector<string> strV;
for (int i = 0; i < k; ++i)
{
strV.push_back(v[i].first);
}
return strV;
}
};
class Solution {
public:
struct Compare
{
bool operator()(const pair<string, int>& x, const pair<string, int>& y)
const
{
// 要注意优先级队列底层是反的,⼤堆要实现⼩于⽐较,所以这⾥次数相等,想要字典序⼩的在前⾯要⽐较字典序⼤的为真
return x.second < y.second || (x.second == y.second && x.first >
y.first);
}
};
vector<string> topKFrequent(vector<string>& words, int k) {
map<string, int> countMap;
for (auto& e : words)
{
countMap[e]++;
}
// 将map中的<单词,次数>放到priority_queue中,仿函数控制⼤堆,次数相同按照字典
序规则排序
priority_queue<pair<string, int>, vector<pair<string, int>>, Compare>
p(countMap.begin(), countMap.end());
vector<string> strV;
for (int i = 0; i < k; ++i)
{
strV.push_back(p.top().first);
p.pop();
}
return strV;
}
};