完整教程:STL map/set 全家桶完全攻略:从原理到实战(含 LeetCode 题解)
在 C++ STL 容器中,map 和 set 系列是高频使用的关联式容器。与 vector、list 等序列式容器不同,它们基于红黑树实现,具备自动排序、高效查找等特性,在去重、键值映射、区间查询等场景中堪称 "神器"。本文将从底层原理、核心接口、使用差异到实战应用,全面拆解 map/set 全家桶的使用技巧,所有示例均来自提供的代码文件及 PDF 文档,确保实用性和准确性。
一、容器分类:序列式 vs 关联式
在学习 map/set 之前,我们需要先明确 STL 容器的核心分类:
| 类型 | 代表容器 | 核心特征 | 访问方式 |
|---|---|---|---|
| 序列式容器 | vector、list、deque | 线性存储,元素间无强关联 | 按存储位置访问 |
| 关联式容器 | map/set、unordered_map/unordered_set | 非线性结构(红黑树 / 哈希表),元素间通过关键字关联 | 按关键字访问 |
本文重点讲解基于红黑树实现的 map/set 系列(包括 multimap/multiset),其核心优势是:
- 增删查效率稳定在 O (log N)
- 迭代遍历为中序遍历,天然有序
- 支持关键字排序、区间查询等高级操作
二、set 系列:有序去重的 "集合神器"
set 系列以关键字为核心,专注于 "集合" 相关操作,分为 set 和 multiset 两个版本。
2.1 核心特性与模板参数
set 的模板声明如下:
template <
class T, // 关键字类型(也是值类型)
class Compare = less, // 比较仿函数(默认升序)
class Alloc = allocator // 空间配置器(默认无需修改)
> class set;
关键特性:
- 底层红黑树实现,元素自动排序
- 不支持重复元素(去重特性)
- 迭代器为 const 属性,无法修改元素(避免破坏红黑树结构)
- 支持正向 / 反向迭代,默认升序遍历
2.2 核心接口使用
(1)构造方式
支持 4 种常用构造,满足不同初始化需求:
// 1. 无参构造
set s1;
// 2. 迭代器区间构造(常用数组转set)
int arr[] = {4,2,7,2,8};
set s2(arr, arr+sizeof(arr)/sizeof(int));
// 3. 拷贝构造
set s3(s2);
// 4. 初始化列表构造
set s4 = {1,3,5,7,9};
(2)增删查操作
#include
#include
using namespace std;
int main() {
set s;
// 插入:重复元素插入失败
s.insert(5);
s.insert(2);
s.insert(7);
s.insert(5); // 插入失败
s.insert({2,8,3,9}); // 列表插入
// 遍历:默认升序,支持范围for
for (auto e : s) {
cout << e << " "; // 输出:2 3 5 7 8 9
}
cout << endl;
// 查找:find返回迭代器,count返回存在个数(0或1)
auto pos = s.find(3);
if (pos != s.end()) {
cout << "找到元素:" << *pos << endl;
}
// 删除:支持迭代器、值、区间三种方式
s.erase(5); // 按值删除
s.erase(s.begin()); // 按迭代器删除
auto itlow = s.lower_bound(7); // >=7的迭代器
auto itup = s.upper_bound(8); // >8的迭代器
s.erase(itlow, itup); // 区间删除
for (auto e : s) {
cout << e << " "; // 输出:3
}
return 0;
}
(3)count 接口判重示例
#include
#include
#include
using namespace std;
int main() {
set s = { 4,2,7,2,8,5,9 };
int x;
cin >> x;
if (s.count(x)) { // count用来统计指定字符在set中的个数
cout << x << "在!" << endl;
}
else {
cout << x << "不存在!" << endl;
}
return 0;
}
(4)lower_bound 与 upper_bound 区间操作示例
#include
#include
using namespace std;
int main()
{
std::set myset;
for (int i = 1; i < 10; i++)
myset.insert(i * 10); // 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;
}
2.3 set 与 multiset 的核心差异
multiset 是 set 的 "允许重复" 版本,核心差异如下:
| 特性 | set | multiset |
|---|---|---|
| 元素重复性 | 不允许重复 | 支持重复元素 |
| find 操作 | 返回唯一元素迭代器 | 返回中序遍历第一个匹配元素 |
| count 操作 | 返回 0 或 1 | 返回元素实际个数 |
| erase 操作(按值) | 删除单个元素 | 删除所有匹配元素 |
multiset 使用示例:
#include
#include
using namespace std;
int main()
{
// 排序但不去重
multiset ms = {4,2,7,2,4,8,4,5,4,9};
for (auto e : ms) {
cout << e << " "; // 输出:2 2 4 4 4 4 5 7 8 9
}
cout << endl;
// 查找并遍历所有重复元素
int x = 4;
auto pos = ms.find(x);
while (pos != ms.end() && *pos == x) {
cout << *pos << " "; // 输出:4 4 4 4
++pos;
}
cout << endl;
// 统计重复个数
cout << "元素" << x << "的个数:" << ms.count(x) << endl; // 输出:4
// 删除所有匹配元素
ms.erase(x);
for (auto e : ms) {
cout << e << " "; // 输出:2 2 5 7 8 9
}
return 0;
}
三、map 系列:键值映射的 "字典利器"
map 系列专注于 "键值对"(key-value)存储,是实现字典、哈希表功能的核心容器,分为 map 和 multimap 两个版本。
3.1 核心特性与模板参数
map 的模板声明如下:
template <
class Key, // 关键字类型
class T, // 映射值类型
class Compare = less, // 关键字比较仿函数
class Alloc = allocator> // 空间配置器
> class map;
关键特性:
- 底层红黑树实现,按 key 自动排序
- 每个节点存储 pair<const Key, T> 键值对
- key 不允许重复,支持通过 key 快速查找 value
- 支持修改 value,但不允许修改 key(避免破坏红黑树结构)
3.2 核心接口与 pair 类型
map 的元素是 pair 类型,常用构造 pair 的方式:
#include
#include
3.3 核心操作:增删查改
(1)operator [] 的强大功能
map 的 operator [] 是多功能复合接口,支持插入、查找、修改三种操作,使用示例:
#include
#include
(2)经典场景:统计元素出现次数
利用 operator [] 的简洁性,可快速实现元素计数:
#include
#include
3.4 map 与 multimap 的核心差异
multimap 支持 key 重复,核心差异如下:
| 特性 | map | multimap |
|---|---|---|
| key 重复性 | 不允许重复 | 支持重复 key |
| operator[] | 支持(插入 / 查找 / 修改) | 不支持(key 重复无法定位唯一 value) |
| find 操作 | 返回唯一 key 的迭代器 | 返回中序第一个匹配 key 的迭代器 |
| 遍历重复 key | 无需特殊处理 | 需用 equal_range 获取迭代器区间 |
multimap 使用示例:
#include
#include
四、实战演练:LeetCode 经典题解
map/set 系列在算法题中应用广泛,以下是 3 道经典 LeetCode 题的最优解,均基于提供的代码实现。
4.1 两个数组的交集(LeetCode 349)
题目:给定两个数组,计算它们的交集(元素唯一)。思路:利用 set 的去重和有序特性,双指针遍历比较。
#include
#include
using namespace std;
class Solution {
public:
vector intersection(vector& nums1, vector& nums2) {
set s1(nums1.begin(), nums1.end());
set s2(nums2.begin(), nums2.end());
vector ret;
auto it1 = s1.begin();
auto it2 = s2.begin();
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;
}
};
4.2 随机链表的复制(LeetCode 138)
题目:复制一个包含随机指针的链表。思路:利用 map 建立原节点与复制节点的映射,快速定位随机指针。
#include
4.3 前 K 个高频单词(LeetCode 692)
题目:统计单词出现频率,返回前 K 个高频单词,频率相同按字典序排序。思路:利用 map 统计词频,自定义仿函数排序后取前 K 个。
#include
#include
五、总结与注意事项
5.1 核心要点总结
- 底层实现:map/set 系列基于红黑树,O (log N) 高效操作,天然有序;
- 去重特性:set/map 不允许重复,multiset/multimap 支持重复;
- 迭代器特性:set 迭代器不可修改元素,map 迭代器不可修改 key;
- operator[]:map 的核心接口,支持插入 / 查找 / 修改,multimap 不支持;
- 适用场景:去重排序、键值映射、元素统计、区间查询、快速判重。
5.2 常见坑点提醒
- 不要尝试修改 set 的元素或 map 的 key,会破坏红黑树结构;
- map 的 operator [] 访问不存在的 key 时,会自动插入默认值,需谨慎使用;
- 遍历 multimap 的重复 key 时,需用 equal_range 获取迭代器区间;
- 算法库的 find 函数(O (N))效率远低于 set/map 自带的 find 函数(O (log N)),优先使用容器自带接口。
map/set 系列是 C++ 开发中不可或缺的工具,掌握其底层原理和使用技巧,能大幅提升代码效率和可读性。建议结合本文示例代码反复练习,重点掌握 operator []、区间操作、mult 系列的差异及算法题应用,真正做到学以致用。
浙公网安备 33010602011771号