在 C++ STL 中,map 和 set 是最常用的关联式容器—— 它们与 vector、list 等序列式容器的核心区别在于:关联式容器通过 “关键字(Key)” 存储和访问数据,而非通过位置。map 和 set 的底层基于红黑树(一种平衡二叉搜索树)实现,保证了增删查操作的时间复杂度为 O (logN),同时支持有序遍历。本文将从 “容器分类→核心使用→实战案例” 的路径,系统讲解 map 和 set 的特性与应用,帮你彻底掌握这两个工业级容器。
一、序列式容器与关联式容器:核心差异
在学习 map 和 set 之前,需先明确 “序列式容器” 与 “关联式容器” 的本质区别 —— 这是理解两者设计思想的关键。
| 特性 | 序列式容器(如 vector、list) | 关联式容器(如 map、set) |
|---|---|---|
| 数据访问方式 | 按存储位置(索引 / 迭代器)访问 | 按关键字(Key)访问 |
| 底层逻辑结构 | 线性结构(数组、链表) | 非线性结构(红黑树) |
| 有序性 | 插入顺序即存储顺序 | 按关键字自动排序 |
| 核心优势 | 随机访问(vector)、高效增删(list) | 高效检索、去重、有序遍历 |
| 典型应用场景 | 存储连续数据、需要频繁随机访问 | 数据去重、关键字映射、频率统计 |
map 和 set 属于 “有序关联式容器”(还有 unordered_map/unordered_set 属于无序关联式容器,底层是哈希表),其有序性和高效检索能力使其成为解决 “去重”“关键字映射” 等问题的首选。
二、set 系列容器:Key 的有序去重容器
set 系列包含set和multiset,核心功能是 “存储关键字(Key),并按 Key 有序排列”,适用于 “仅需判断 Key 存在性” 的场景(如去重、交集计算)。
2.1 set 的核心特性
去重 + 有序:set 会自动对 Key 去重,并按 Key 的升序(默认)排列;
不可修改 Key:set 的迭代器是const_iterator,无法通过迭代器修改 Key(修改会破坏红黑树结构);
底层是红黑树:增删查时间复杂度 O (logN),中序遍历即有序遍历。
2.2 set 的核心接口实战
set 的接口设计简洁,重点掌握 “构造→增删查→遍历” 即可。
2.2.1 构造与遍历
set 支持无参构造、迭代器区间构造、拷贝构造和初始化列表构造,遍历通过迭代器或范围 for 实现(默认升序)。
代码示例:
#include <iostream>
#include <set>
using namespace std;
int main() {
// 1. 初始化列表构造(自动去重+升序)
set<int> s1 = {5, 2, 7, 5, 3};
// 2. 迭代器遍历(升序输出:2 3 5 7)
for (auto it = s1.begin(); it != s1.end(); ++it) {
// *it = 10; // 错误:迭代器不可修改
cout << *it << " ";
}
cout << endl;
// 3. 降序set(传入greater<int>仿函数)
set<int, greater<int>> s2 = {5, 2, 7, 5, 3};
// 4. 范围for遍历(降序输出:7 5 3 2)
for (auto e : s2) {
cout << e << " ";
}
cout << endl;
return 0;
}
2.2.2 增删查操作
set 的增删查接口围绕 Key 展开,核心接口如下:
| 接口名称 | 功能说明 |
|---|---|
insert(const Key& val) | 插入 Key,成功返回pair<iterator, bool>(first 是迭代器,second 表示是否插入成功) |
erase(const Key& val) | 删除 Key,返回删除的元素个数(0 表示不存在) |
erase(iterator pos) | 删除迭代器指向的元素,返回下一个有效迭代器 |
find(const Key& val) | 查找 Key,返回对应迭代器(不存在返回end()) |
count(const Key& val) | 返回 Key 的个数(set 中仅 0 或 1,用于快速判断存在性) |
lower_bound(val) | 返回第一个≥val 的迭代器 |
upper_bound(val) | 返回第一个 > val 的迭代器 |
代码示例(增删查):
int main() {
set<int> s = {4, 2, 7, 8, 5};
// 1. 插入(重复插入失败)
auto ret1 = s.insert(3); // 成功:ret1.second = true
auto ret2 = s.insert(4); // 失败:ret1.second = false
// 2. 查找(存在则返回迭代器,不存在返回end())
int x = 5;
auto pos = s.find(x);
if (pos != s.end()) {
cout << "找到" << x << endl;
} else {
cout << x << "不存在" << endl;
}
// 3. 删除(按Key删除)
int del_num = s.erase(2); // 删除2,返回1(成功)
if (del_num == 0) {
cout << "删除失败" << endl;
}
// 4. 区间删除(删除[3,6)的元素:3、4、5)
auto it_low = s.lower_bound(3); // 第一个≥3的迭代器
auto it_up = s.upper_bound(6); // 第一个>6的迭代器
s.erase(it_low, it_up);
// 遍历结果:7 8
for (auto e : s) {
cout << e << " ";
}
return 0;
}
2.3 set 与 multiset 的差异
multiset是 set 的 “允许重复 Key” 版本,核心差异如下:
| 特性 | set | multiset |
|---|---|---|
| Key 重复性 | 不允许重复 | 允许重复 |
insert返回值 | pair<iterator, bool> | 仅返回插入位置的迭代器 |
find行为 | 返回任意一个匹配的 Key(仅一个) | 返回中序遍历的第一个匹配 Key |
count行为 | 返回 0 或 1 | 返回匹配 Key 的实际个数 |
erase(Key)行为 | 删除 1 个匹配 Key(若存在) | 删除所有匹配 Key |
代码示例(multiset):
int main() {
// 允许重复Key,排序但不去重
multiset<int> ms = {4, 2, 7, 2, 4, 4};
// 1. 遍历:2 2 4 4 4 7
for (auto e : ms) {
cout << e << " ";
}
cout << endl;
// 2. find返回中序第一个匹配Key(找4,返回第一个4的迭代器)
auto pos = ms.find(4);
while (pos != ms.end() && *pos == 4) {
cout << *pos << " "; // 输出:4 4 4
++pos;
}
cout << endl;
// 3. count返回实际个数(4出现3次)
cout << "4的个数:" << ms.count(4) << endl;
// 4. erase(Key)删除所有匹配Key(删除所有4)
ms.erase(4);
// 遍历结果:2 2 7
for (auto e : ms) {
cout << e << " ";
}
return 0;
}
2.4 set 实战案例:两个数组的交集(LeetCode 349)
需求:给定两个数组,计算它们的交集(结果需去重)。思路:用 set 对两个数组去重并排序,再用双指针遍历求交集。
代码实现:
#include <vector>
#include <set>
using namespace std;
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
// 1. 用set去重并排序
set<int> s1(nums1.begin(), nums1.end());
set<int> s2(nums2.begin(), nums2.end());
vector<int> ret;
auto it1 = s1.begin(), it2 = s2.begin();
// 2. 双指针遍历求交集
while (it1 != s1.end() && it2 != s2.end()) {
if (*it1 < *it2) {
++it1;
} else if (*it1 > *it2) {
++it2;
} else {
ret.push_back(*it1);
++it1;
++it2;
}
}
return ret;
}
};
三、map 系列容器:Key-Value 的有序映射容器
map 系列包含map和multimap,核心功能是 “存储 Key-Value 键值对,按 Key 有序排列”,适用于 “关键字映射” 场景(如字典、频率统计)。
3.1 map 的核心特性
Key 唯一 + 有序:map 的 Key 唯一且按升序排列,Value 可修改;
存储键值对:底层红黑树的节点存储pair<const Key, Value>(Key 不可修改,Value 可修改);
operator[]多功能接口:支持插入、查找、修改 Value(map 的核心特色);
底层是红黑树:增删查时间复杂度 O (logN),中序遍历按 Key 有序。
3.2 map 的核心接口实战
map 的接口与 set 类似,但需关注 “键值对(pair)” 的处理,重点掌握insert和operator[]。
3.2.1 pair 类型:map 的键值对载体
map 存储的是pair<const Key, Value>类型,pair是 STL 中的模板结构体,包含两个成员:
first:Key(const 类型,不可修改);
second:Value(可修改)。
创建 pair 的四种方式:
// 1. 直接构造
pair<string, int> kv1("apple", 5);
// 2. 用make_pair函数(推荐,自动推导类型)
auto kv2 = make_pair("banana", 3);
// 3. 初始化列表(C++11后推荐)
pair<string, int> kv3 = {"cherry", 2};
// 4. map的value_type别名
map<string, int>::value_type kv4("date", 4);
3.2.2 构造与遍历
map 的构造方式与 set 类似,遍历需通过迭代器访问pair的first(Key)和second(Value)。
代码示例:
#include <iostream>
#include <map>
using namespace std;
int main() {
// 1. 初始化列表构造(按Key升序排列)
map<string, string> dict = {
{"left", "左边"},
{"right", "右边"},
{"insert", "插入"}
};
// 2. 迭代器遍历(输出Key:Value)
auto it = dict.begin();
while (it != dict.end()) {
// 方式1:解引用迭代器访问pair
cout << (*it).first << ":" << (*it).second << " ";
// 方式2:用->访问pair(更简洁,推荐)
// cout << it->first << ":" << it->second << " ";
++it;
}
// 输出:insert:插入 left:左边 right:右边(按Key字典序)
cout << endl;
return 0;
}
3.2.3 增删查与operator[]
map 的增删查接口与 set 类似,但insert插入的是pair,且operator[]是 map 的 “万能接口”—— 支持插入、查找、修改 Value。
1. insert插入键值对
insert返回pair<iterator, bool>:
first:指向 Key 所在节点的迭代器(无论插入成功与否);
second:true表示插入成功(Key 不存在),false表示插入失败(Key 已存在)。
代码示例:
int main() {
map<string, int> countMap;
// 插入方式1:直接插入pair
countMap.insert(pair<string, int>("apple", 1));
// 插入方式2:make_pair(推荐)
countMap.insert(make_pair("banana", 2));
// 插入方式3:初始化列表(推荐)
countMap.insert({"cherry", 3});
// 重复插入(Key已存在,插入失败)
auto ret = countMap.insert({"apple", 5});
if (!ret.second) {
cout << "apple已存在,当前计数:" << ret.first->second << endl;
}
return 0;
}
2. operator[]:插入、查找、修改三合一
operator[]的底层实现依赖insert,逻辑如下:
Value& operator[](const Key& k) {
// 1. 尝试插入pair<k, Value()>(Value()是默认构造值,如int()=0)
auto ret = insert({k, Value()});
// 2. 返回Value的引用(无论插入成功与否)
return ret.first->second;
}
operator[]的三种用法:
int main() {
map<string, int> countMap;
// 1. 插入:Key不存在时,插入{Key, 0}并返回Value引用
countMap["apple"] = 1; // 插入{apple, 1}
// 2. 修改:Key存在时,返回Value引用并修改
countMap["apple"]++; // apple的Value变为2
// 3. 查找:Key存在时,返回Value引用
cout << "apple的计数:" << countMap["apple"] << endl; // 输出2
// 4. 仅插入:Key不存在时,插入{Key, 默认值}
countMap["banana"]; // 插入{banana, 0}
return 0;
}
3. 查找与删除
map 的查找和删除接口与 set 类似,但查找返回的是指向pair的迭代器,可通过second修改 Value。
代码示例:
int main() {
map<string, int> countMap = {{"apple", 2}, {"banana", 1}, {"cherry", 3}};
// 1. 查找(返回指向pair的迭代器)
string key = "apple";
auto pos = countMap.find(key);
if (pos != countMap.end()) {
cout << key << "的计数:" << pos->second << endl;
// 修改Value
pos->second = 5;
}
// 2. 删除(按Key删除)
int del_num = countMap.erase("banana"); // 删除成功,返回1
if (del_num == 0) {
cout << "删除失败" << endl;
}
// 遍历结果:apple:5 cherry:3
for (auto& e : countMap) {
cout << e.first << ":" << e.second << " ";
}
return 0;
}
3.3 map 与 multimap 的差异
multimap是 map 的 “允许 Key 重复” 版本,核心差异如下:
| 特性 | map | multimap |
|---|---|---|
| Key 重复性 | 不允许重复 | 允许重复 |
insert返回值 | pair<iterator, bool> | 仅返回插入位置的迭代器 |
operator[]支持 | 支持(插入、查找、修改) | 不支持(Key 重复,无法确定修改哪个) |
find行为 | 返回任意一个匹配的 Key | 返回中序遍历的第一个匹配 Key |
count行为 | 返回 0 或 1 | 返回匹配 Key 的实际个数 |
代码示例(multimap):
int main() {
// 允许Key重复,按Key有序
multimap<int, string> mm = {{1, "a"}, {2, "b"}, {1, "c"}, {2, "d"}};
// 遍历:1:a 1:c 2:b 2:d
for (auto& e : mm) {
cout << e.first << ":" << e.second << " ";
}
cout << endl;
// find返回中序第一个匹配Key(找1,返回{1,"a"}的迭代器)
auto pos = mm.find(1);
while (pos != mm.end() && pos->first == 1) {
cout << pos->first << ":" << pos->second << " "; // 输出1:a 1:c
++pos;
}
return 0;
}
3.4 map 实战案例:单词频率统计
需求:统计数组中各单词的出现次数,并按单词字典序输出。思路:用 map 的operator[]快速统计频率,中序遍历即按字典序输出。
代码实现:
#include <iostream>
#include <map>
#include <string>
using namespace std;
int main() {
string arr[] = {"apple", "banana", "apple", "cherry", "apple", "banana"};
map<string, int> countMap;
// 统计频率(用operator[]简洁实现)
for (const auto& word : arr) {
countMap[word]++; // 插入/修改频率
}
// 按单词字典序输出频率
for (const auto& e : countMap) {
cout << e.first << ":" << e.second << endl;
}
// 输出:
// apple:3
// banana:2
// cherry:1
return 0;
}
四、map 与 set 的核心区别与适用场景
| 特性 | set | map |
|---|---|---|
| 存储内容 | 单个 Key | Key-Value 键值对 |
| 核心用途 | 去重、有序遍历、判断存在性 | 关键字映射、频率统计、字典 |
| Key 修改 | 不可修改(迭代器是 const) | Key 不可修改,Value 可修改 |
| 特有接口 | 无特殊接口 | operator[](插入 / 查找 / 修改) |
| 典型场景 | 数组交集、环形链表检测 | 单词翻译、随机链表复制 |
五、总结
map 和 set 是 C++ STL 中最实用的关联式容器,其核心价值在于 “有序性” 和 “高效检索”:
- set:专注于 Key 的去重与有序,适用于 “仅需判断存在性” 的场景,如去重、交集计算;
- map:专注于 Key-Value 的有序映射,适用于 “关键字关联” 场景,如字典、频率统计;
- multiset/multimap:允许 Key 重复,适用于 “Key 不唯一” 的场景,但需注意
operator[]的支持差异。
1514

被折叠的 条评论
为什么被折叠?



