目录
[0. 序列式容器和关联式容器](#0. 序列式容器和关联式容器)
[1. set 的介绍](#1. set 的介绍)
[2. set 的增删查](#2. set 的增删查)
[3. multiset](#3. multiset)
[1. map介绍](#1. map介绍)
[2. pair](#2. pair)
[3. map的增删查](#3. map的增删查)
[4. map的 操作](#4. map的 [ ] 操作)
[5. multimap](#5. multimap)
0. 序列式容器和关联式容器
-
序列式容器
-
本质 :元素按照插入的顺序 或指定的位置线性排列。
-
特点:每个元素都有固定的物理位置(索引),你可以通过位置(下标)直接访问它;两个位置存储的值之间一般没有紧密的关联关系。
-
代表 :
vector(动态数组)、list(双向链表)、deque(双端队列)、array(静态数组)、forward_list(单向链表)。
-
-
关联式容器
-
本质 :元素按照 Key(键) 自动排序,或者通过哈希函数映射。
-
特点:没有所谓的"物理位置"概念(不能用下标访问),你想找元素,只能通过它的 Key 去查找;它是非线性结构,两个位置有紧密的关联关系,交换一下,它的存储结构就被破坏了。
-
代表 :
map/set(底层红黑树,有序)、unordered_map/unordered_set(底层哈希表,无序)。
-
一、set
1. set 的介绍
是一个"用平衡二叉搜索树实现的、自动去重、自动排序的集合容器"。

cpp
#include <set>
std::set<int> s = {5, 1, 3, 3, 2};
// s 里实际是:{1, 2, 3, 5}
// 自动排序 ↑ 自动去重 ↑
set比较重要的两个特性:
- 只支持增删查;
- 时间复杂度是 O(logn)。
- 不允许修改key
cpp
std::set<int>::iterator it = s1.begin();
while (it != s1.end()) {
// *it += 1; // 不支持修改key
++it;
}
cout << endl;
2. set 的增删查
(1)insert、erase、find、count...
cpp
int main(){
std::set<int> s;
s.insert(10); // 插入,重复则忽略
s.insert({ 3, 7, 3 }); // 批量插入,3 只留一个
s.count(10); // 1 存在 / 0 不存在(set 里 count 非 0 即 1)
s.find(10); // 返回迭代器,找不到返回 s.end()
s.erase(10); // 按值删,返回删除个数
s.erase(s.find(7)); // 按迭代器删
s.empty(); // 是否为空
s.size(); // 元素个数
s.clear(); // 清空
return 0;
}
(2)lower_bound、upper_bound
- lower_bound(k):下界(闭区间), 返回第一个 >= k 的元素。
- upper_bound(k):上界(开区间),返回第一个 >k 的元素。
cpp
std::set<int> s1;
s1.insert({ 3,4,2,4,2,1,6,7,8,9,10 });
for (auto e : s1) {
cout << e << " ";
}
cout << endl;
// 删除[3,5]
// >=3,左闭区间位置
auto it1 = s1.lower_bound(3);
// > 5 右开区间位置
auto it2 = s1.upper_bound(5);
s1.erase(it1, it2);
for (auto e : s1) {
cout << e << " "; // 1 2 6 7 8 9 10
}
cout << endl;
(3)find的时间复杂度

( find返回的是中序的第一个。)
- 算法库里的 find 用的是暴力算法,时间复杂度为 O(N)。
- set 底层是搜索二叉树,find的时间复杂度是 O(logN)。
3. multiset
multiset = 允许 key 重复的 set 。
multiset 和 set 的区别:
| 特性 | set |
multiset |
|---|---|---|
| key 重复 | ❌ 不允许 | ✅ 允许 |
| count(k) | 返回 0 或 1 | 返回 0 ~ n(真实个数) |
| find() | 查找中序的第一个 | |
| erase(k) | 删 0 或 1 个 | 删掉全部 k |
| erase(it) | 删 1 个 | 删 1 个 |
| 迭代器可改 | ❌ const |
❌ const |
| 头文件 | <set> |
<set> |
cpp
void test_set() {
// 相比set不同的是,multiset是排序,但是不去重
multiset<int> s = { 1,2,3,2,4,4,5,6,2,2};
auto it = s.begin();
while (it != s.end())
{
cout << *it << " ";
++it;
}
cout << endl;
// 相比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;
}
二、map
类型别名:

1. key_type (键的类型)
第一个模板参数。就是在声明 map 时传入的第一个类型,如果是 map<string, int> m;,那么 key_type 就是 string。
2. mapped_type (值的类型)
第二个模板参数。就是在声明 map 时传入的第二个类型,如果是 map<string, int> m;,那么 mapped_type 就是 int。
3. value_type (底层真正存储的元素类型)- 即pair<const key_type, mapped_type>。
1. map介绍
std::map 的功能和 set 类似,会自动排序,主要用于快速查找,去重等;它的底层实现是红黑树。
与 set 不同的是,map 里存储的不是单独的key,而是键值对( key-value ),
key用于排序和标识,value用于存储与key关联的内容。
- map中key不允许重复,value可以重复
- key不允许修改,value可以修改
cpp
map<string, string>::iterator it = dict.begin();
while (it != dict.end()) {
cout << it->first << ":" << it->second << endl;
//it->first = "x"; // 不能修改key
it->second = "x"; // 可以修改value
++it;
}
cout << endl;
map的定义:

2. pair
map底层的红黑树节点中的数据,使用pair存储键值对数据。
cpp
template<class K, class V>
struct BSTreeNode
{
BSTreeNode<K, V>* _left;
BSTreeNode<K, V>* _right;
pair<const K,V>_kv; //map里是这样存的
// ...
};
/////// pair ///////
typedef pair<const Key, T> value_type;
template <class T1, class T2>
struct pair
{
typedef T1 first_type;
typedef T2 second_type;
T1 first; // key
T2 second; // value
//...
};
注意:pair未支持流插入、流提取操作。
3. map的增删查
(1)插入

cpp
void test_map1() {
map<string, string> dict;
dict.insert(pair<string, string>("sort", "排序"));
dict.insert(pair<string, string>("insert", "插入"));
dict.insert({ "string", "字符串" }); // 隐式类型转换
pair<string, string> kv1 = { "string", "字符串" };
for (auto& kv : dict)
{
cout << kv.first << ":" << kv.second << endl;
}
cout << endl;
return 0;
}
insert插入的是一个pair对象,插入的方式有以下几种:
cppmap<string, string> dict = { {"hello","你好"},{"insert","插入"} }; // insert 插入的4种方式 pair<string, string> kv1 = { "left","左边" }; dict.insert(kv1); dict.insert(pair<string, string>("right", "右边")); dict.insert(make_pair("erase", "删除"); dict.insert({ "world","世界" });make_pair 是c++11前的一种写法,它是一个函数模板,返回pair类型的对象,了解即可。
(2)查找

cpp
string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };
map<string, int> countMap;
for (auto& str : arr)
{
auto it = countMap.find(str);
if (it == countMap.end())
countMap.insert({ str,1 });
else
it->second++;
}
for (auto& kv : countMap)
{
cout << kv.first << ":" << kv.second << endl;
}
cout << endl;
注意:map类支持 ,所以上述代码也可以改为:
cppstring arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" }; map<string, int> countMap; for (auto& str : arr) { countMap[str]++; // 重载了[] } for (auto& kv : countMap) { cout << kv.first << ":" << kv.second << endl; } cout << endl;
(3)删除的方式和set类似,不做赘述。
4. map的 操作
若 key 不在 map 中,插入 value_type(key, T()) 并返回其 value 的引用;否则返回已存在元素的 value 引用。
map的 支持查询,修改,插入的功能。
cpp
map<string, string> dict;
dict["hello"] = "你好"; // 可以插入
cout << dict["hello"] << endl; // 可以查找
dict["hello"] = "您好"; // 可以修改
cout << dict["hello"] << endl;
原理:
std::map::operator 的定义:
cpp
mapped_type& operator[] (const key_type& k) {
return (*((this->insert(make_pair(k, mapped_type()))).first)).second;
}
1. 第一层:this->insert(...)
- 调用 map 的 insert 函数。
- (插入key,并调用第二个模板参数(即
T)的默认构造,生成一个默认值mapped_type()。例如 map<string, int>,这就相当于生成了一个 int(0))。 - 重点来了 :insert 的返回值是一个 pair<iterator, bool>。
- .first 是一个迭代器 ,指向 map 中键为 k 的那个元素(不管这个元素是刚刚新插入的,还是原本就已经存在的)。
- .second 是一个布尔值,表示是否真的插入了(true 表示原来没有,新插了;false 表示原来就有,没插)。
4. 第二层:(...).first
- 我们取出了 insert 返回的 pair 中的 .first,也就是那个迭代器。
- 此时我们手里拿着一个指向 pair<const Key, T> 的迭代器。
- 注:因为我们不关心到底插没插进去(布尔值),只关心能不能拿到那个位置的数据,所以这里直接忽略了 .second。
5. 第三层:*(...)
-
对迭代器进行解引用(
*),拿到 map 中真正存储的那个元素本身。 -
这个元素的类型是 pair<const Key, T>。
6. 第四层:(...).second 和 return
-
取出该元素的 .second,也就是我们真正要操作的 value(
T类型)。 -
因为迭代器解引用拿到的是引用,所以这里的 .second 也是引用,return 直接把它作为一个 T& 返回出去。
-
返回引用就可以支持直接赋值等对value的修改操作。
cpp
map<string, string> dict;
dict["hello"] = "你好"; // 可以插入
cout << dict["hello"] << endl; // 可以查找
dict["hello"] = "您好"; // 可以修改
cout << dict["hello"] << endl;
一句话总结:
它尝试把 (k, T()) 插入 map,如果 k 已经存在则不插入;不管插没插进去,insert 都会返回一个指向该 k 对应元素的迭代器,然后把这个元素的 second(也就是 value)以引用的形式返回。
5. multimap
和map类似,multimap支持值冗余,不支持operator 。

multimap允许重复插入,map的第二次插入无效,value不会被覆盖成新的值。
三、map和set的区别
| 特性 | set | map |
|---|---|---|
| 存储内容 | 只有 Key(单值) | Key + Value(键值对 pair) |
| 取值方式 | 只能获取元素本身 | 可以通过 Key 获取对应的 Value |
| operator\[\] | ❌ 不支持 | ✅ 支持(mkey 获取 value) |
| 迭代器取值 | *it (直接就是值) | it->first(Key), it->second(Value) |
| 内存开销 | 较小(只存一个值) | 较大(存两个值) |
| 典型用途 | 去重、排序、判断是否存在、集合运算(交并差) | 字典、计数、键值映射(如 ID 找人) |