C++ 中 std::set 用法详解(2025-2026 最新标准视角)
std::set 是 C++ STL(标准模板库)中最常用的有序关联容器之一,属于集合(set)的实现。
一、set 的核心特点(必须先记住)
| 特性 | 说明 | 实际意义 |
|---|---|---|
| 元素唯一 | 自动去重,插入重复元素会被忽略 | 天然适合“集合”概念 |
| 自动排序 | 默认按升序排列(使用 < 比较) | 插入后无需手动排序 |
| 底层实现 | 红黑树(平衡二叉搜索树) | 插入/删除/查找均为 O(log n) |
| 不可修改元素 | 只能插入/删除,不能直接修改已有元素的值(因为会破坏排序) | 想改值 → 删除旧的 + 插入新的 |
| 有序遍历 | 迭代器天然有序(从 begin() 到 end() 就是升序) | 适合需要有序输出的场景 |
| 不支持随机访问 | 没有 [] 运算符,只能通过迭代器或 find() 访问 | 不能像 vector 那样用下标 |
一句话总结:
set = 自动排序 + 自动去重 + 高效查找/插入/删除 的有序集合
二、头文件与基本声明
#include <set>
#include <iostream>
using namespace std;
最常用声明方式:
set<int> s; // 默认升序
set<int, greater<int>> s2; // 降序
set<string> names; // 存储字符串(按字典序)
三、常用成员函数一览(高频使用)
| 函数 | 功能 | 时间复杂度 | 示例用法 | 返回值说明 |
|---|---|---|---|---|
| insert(x) | 插入元素 x | O(log n) | s.insert(5); | pair(插入成功返回 true) |
| erase(x) / erase(it) | 删除元素 x 或迭代器位置的元素 | O(log n) | s.erase(5); s.erase(s.find(3)); | 删除的元素个数(通常 0 或 1) |
| find(x) | 查找元素 x | O(log n) | auto it = s.find(10); | 找到返回迭代器,没找到返回 end() |
| count(x) | 元素 x 出现的次数(只有 0 或 1) | O(log n) | if (s.count(7)) cout << “存在”; | 0 或 1 |
| size() / empty() | 大小 / 是否为空 | O(1) | if (s.empty()) … | — |
| clear() | 清空所有元素 | O(n) | s.clear(); | — |
| begin() / end() | 指向第一个 / 最后一个后一个元素 | O(1) | for(auto it = s.begin(); it != s.end(); ++it) | 有序迭代器 |
| lower_bound(x) | 第一个 ≥ x 的元素 | O(log n) | auto it = s.lower_bound(10); | ≥ x 的第一个位置 |
| upper_bound(x) | 第一个 > x 的元素 | O(log n) | auto it = s.upper_bound(10); | > x 的第一个位置 |
| equal_range(x) | [lower_bound(x), upper_bound(x)) | O(log n) | auto [l, r] = s.equal_range(5); | 等于 x 的范围(set 中最多一个) |
四、代码实战示例(最常用场景)
1. 基本插入 + 遍历
set<int> s;
s.insert(30);
s.insert(10);
s.insert(50);
s.insert(20);
s.insert(10); // 重复元素,自动忽略
// 遍历(天然有序)
for (int x : s) {
cout << x << " "; // 输出:10 20 30 50
}
cout << endl;
2. 查找与删除
if (s.find(20) != s.end()) {
cout << "找到 20\n";
s.erase(20); // 删除 20
}
if (s.count(100) == 0) {
cout << "100 不存在\n";
}
3. lower_bound / upper_bound 经典用法(区间查询)
// 找出 [20, 40] 之间的所有元素
auto l = s.lower_bound(20);
auto r = s.upper_bound(40);
for (auto it = l; it != r; ++it) {
cout << *it << " "; // 输出 20 30
}
4. 自定义比较器(降序 / 自定义结构体)
// 降序 set
set<int, greater<int>> desc;
desc.insert({5, 1, 8, 3}); // 输出顺序:8 5 3 1
// 自定义结构体按某个字段排序
struct Student {
string name;
int score;
};
auto cmp = [](const Student& a, const Student& b) {
return a.score > b.score; // 按分数降序
};
set<Student, decltype(cmp)> students(cmp);
5. pair / 多关键字排序(非常常见)
set<pair<int, string>> s; // 默认先按 int 升序,再按 string 升序
s.insert({90, "Alice"});
s.insert({85, "Bob"});
s.insert({90, "Charlie"}); // 90 的 Alice 在 Charlie 前面(string 字典序)
五、set vs unordered_set vs multiset
| 容器 | 是否有序 | 是否允许重复 | 查找/插入复杂度 | 适用场景 |
|---|---|---|---|---|
| set | 是 | 否 | O(log n) | 需要有序 + 去重 |
| unordered_set | 否 | 否 | 平均 O(1) | 只需快速去重 + 不关心顺序 |
| multiset | 是 | 是 | O(log n) | 需要有序 + 允许重复元素 |
六、2026 年使用建议(面试 + 生产)
- 需要有序输出或范围查询 → 首选 set
- 只关心去重且不关心顺序 → 用 unordered_set(更快)
- 需要有序 + 允许重复 → 用 multiset
- 元素是自定义类型 → 必须提供
<运算符 或 自定义比较器 - 频繁增删 + 元素量大(>10^6) → 优先考虑 unordered_set(但注意哈希冲突最坏 O(n))
- 面试高频题:用 set 实现滑动窗口中位数、最近请求次数等
七、最简总结(背下来就够面试)
- set:有序、唯一、红黑树、O(log n) 操作
- 插入重复元素自动忽略
- 不可通过迭代器修改元素值(const)
- lower_bound / upper_bound 是最强范围查询利器
如果你现在有具体的使用场景(比如怎么用 set 去重排序、怎么实现有序去重输出、自定义比较器报错等),可以贴代码或描述需求,我帮你写最优解法。