set 是 C++ STL 中的关联式容器,核心特性是「有序、唯一」—— 存储的元素自动按升序排列,且不允许重复值。底层基于红黑树(平衡二叉搜索树)实现,保证插入、删除、查找操作的时间复杂度均为 O(logn),是处理「有序唯一集合」的首选容器。
一、核心特性
| 特性 | 说明 |
|---|---|
| 有序性 | 默认按元素值升序排列,可自定义排序规则(如降序、结构体自定义排序) |
| 唯一性 | 容器内无重复元素,插入重复值会被忽略(insert 返回值可判断是否插入成功) |
| 底层实现 | 红黑树(平衡二叉搜索树),避免 BST 退化,保证操作效率 |
| 迭代器类型 | 双向迭代器(bidirectional iterator),支持 ++/--,不支持随机访问(如 it+3) |
| 内存与迭代器 | 插入 / 删除元素时,除被删除元素的迭代器外,其他迭代器不失效 |
| 无下标访问 | 没有 [] 运算符和 at() 方法,只能通过迭代器遍历 / 访问元素 |
二、基础使用(必掌握)
1. 头文件与命名空间
使用 set 必须包含头文件 <set>,且依赖 std 命名空间:
#include <iostream>
#include <set> // 核心头文件
using namespace std;
2. 初始化(常见方式)
// 方式1:空set(默认升序)
set<int> s1;
// 方式2:初始化列表(C++11及以上)
set<int> s2 = {3, 1, 4, 2}; // 自动排序为 {1,2,3,4}
// 方式3:迭代器范围初始化
int arr[] = {5, 3, 7};
set<int> s3(arr, arr + sizeof(arr)/sizeof(int)); // {3,5,7}
// 方式4:自定义排序(降序)
set<int, greater<int>> s4 = {3,1,4,2}; // {4,3,2,1}
3. 核心成员函数(高频)
(1)插入元素:insert()
- 插入成功:返回
pair<iterator, bool>,bool为true,iterator指向插入位置; - 插入失败(重复):
bool为false,iterator指向已存在的重复元素。
set<int> s;
// 插入单个元素
auto res1 = s.insert(3); // res1.second = true,s={3}
auto res2 = s.insert(3); // res2.second = false,res2.first 指向已有的3
// 批量插入(C++11)
s.insert({1, 2, 4}); // s={1,2,3,4}
(2)查找元素:find()
- 找到:返回指向该元素的迭代器;
- 未找到:返回
s.end()(尾后迭代器,不指向任何元素)。
set<int> s = {1,2,3,4};
auto it = s.find(3);
if (it != s.end()) {
cout << "找到元素:" << *it << endl; // 输出:找到元素:3
}
it = s.find(5);
if (it == s.end()) {
cout << "未找到元素5" << endl;
}
(3)删除元素:erase()
支持三种删除方式,需注意迭代器失效问题:
set<int> s = {1,2,3,4,5};
// 方式1:按值删除(返回删除的元素个数,set中只能是0或1)
int cnt = s.erase(3); // cnt=1,s={1,2,4,5}
// 方式2:按迭代器删除(删除单个元素)
auto it = s.find(4);
if (it != s.end()) {
s.erase(it); // s={1,2,5}
}
// 方式3:按迭代器范围删除(删除[first, last)区间)
s.erase(s.begin(), s.find(5)); // 删除1、2,s={5}
(4)遍历元素
因无下标访问,只能通过迭代器或范围 for(C++11) 遍历:
set<int> s = {3,1,4,2};
// 方式1:普通迭代器(升序)
for (set<int>::iterator it = s.begin(); it != s.end(); ++it) {
cout << *it << " "; // 输出:1 2 3 4
}
cout << endl;
// 方式2:范围for(简化版)
for (int val : s) {
cout << val << " "; // 输出:1 2 3 4
}
cout << endl;
// 方式3:反向迭代器(降序)
for (set<int>::reverse_iterator it = s.rbegin(); it != s.rend(); ++it) {
cout << *it << " "; // 输出:4 3 2 1
}
cout << endl;
(5)其他常用函数
set<int> s = {1,2,3,4};
s.size(); // 返回元素个数:4
s.empty(); // 判断是否为空:false
s.clear(); // 清空所有元素,s变为空
s.count(2); // 统计元素个数(set中只能是0或1):1
s.lower_bound(2); // 返回第一个≥2的元素迭代器(指向2)
s.upper_bound(2); // 返回第一个>2的元素迭代器(指向3)
三、进阶用法
1. 自定义排序规则
默认 set 按 < 升序排列,可通过「函数对象」或「lambda 表达式(C++11)」修改排序规则:
(1)降序排列
// 方式1:使用STL自带的greater
set<int, greater<int>> s = {3,1,4,2};
for (int val : s) {
cout << val << " "; // 输出:4 3 2 1
}
// 方式2:自定义函数对象
struct MyCompare {
bool operator()(int a, int b) const {
return a > b; // 降序:a>b时,a排在b前面
}
};
set<int, MyCompare> s2 = {3,1,4,2}; // 同样输出4 3 2 1
(2)自定义结构体排序
存储结构体时,必须显式定义排序规则(否则编译器无法比较):
// 定义结构体:存储学生姓名和分数
struct Student {
string name;
int score;
// 构造函数
Student(string n, int s) : name(n), score(s) {}
};
// 自定义排序规则:按分数降序,分数相同按姓名升序
struct CompStudent {
bool operator()(const Student& a, const Student& b) const {
if (a.score != b.score) {
return a.score > b.score;
}
return a.name < b.name;
}
};
int main() {
set<Student, CompStudent> s;
s.insert(Student("Alice", 90));
s.insert(Student("Bob", 85));
s.insert(Student("Charlie", 90));
// 遍历:Alice(90) → Charlie(90) → Bob(85)
for (const auto& stu : s) {
cout << stu.name << " : " << stu.score << endl;
}
return 0;
}
2. set 的变体容器
(1)multiset:允许重复元素
multiset 与 set 几乎一致,唯一区别是允许存储重复元素:
insert始终成功(无返回bool的重载);count(val)可返回重复元素的个数;erase(val)会删除所有值为val的元素;- 底层仍为红黑树,操作复杂度
O(logn)。
示例:
multiset<int> ms = {1,2,2,3,3,3};
cout << ms.count(2) << endl; // 输出:2
ms.erase(2); // 删除所有2,ms={1,3,3,3}
auto it = ms.find(3);
if (it != ms.end()) {
ms.erase(it); // 仅删除第一个3,ms={1,3,3}
}
(2)unordered_set:无序唯一集合
unordered_set 是「哈希表」实现的无序容器,核心特点:
- 无序性(不排序,存储顺序随机);
- 唯一性(无重复元素);
- 查找 / 插入 / 删除平均
O(1)复杂度(哈希冲突时退化为O(n)); - 不支持自定义排序(因无序),仅支持默认哈希规则(可自定义哈希函数)。
对比 set 和 unordered_set:
| 特性 | set | unordered_set |
|---|---|---|
| 底层实现 | 红黑树 | 哈希表 |
| 有序性 | 升序(可自定义) | 无序 |
| 查找复杂度 | O(logn) | 平均 O (1),最坏 O (n) |
| 迭代器 | 双向迭代器 | 正向迭代器 |
| 适用场景 | 需要有序集合 | 高频查找,无需排序 |
示例:
#include <unordered_set>
unordered_set<int> us = {3,1,4,2};
for (int val : us) {
cout << val << " "; // 输出顺序随机,如:1 3 2 4
}
cout << us.find(2) != us.end() << endl; // 输出:1(找到)
四、使用注意事项
-
迭代器限制:
set的迭代器是「const 迭代器」(逻辑上):即使声明为非 const 迭代器,也不能修改元素值(因为修改会破坏红黑树的有序性);- 不支持随机访问,如
it + 2会编译报错,只能通过++/--移动。
-
插入重复元素:
set.insert(val)返回pair<iterator, bool>,可通过second判断是否插入成功;- 若需存储重复元素,直接用
multiset,而非手动判断后插入。
-
效率对比:
- 若仅需「去重」且无需有序,优先用
unordered_set(查找更快); - 若需「有序 + 去重」,用
set; - 若需「有序 + 允许重复」,用
multiset。
- 若仅需「去重」且无需有序,优先用
-
自定义类型的要求:
- 存储自定义结构体 / 类时,必须提供排序规则(
set/multiset)或哈希规则(unordered_set); - 排序规则的函数对象必须是「可调用的」且返回
bool,参数为 const 引用(避免拷贝)。
- 存储自定义结构体 / 类时,必须提供排序规则(
五、典型使用场景
- 去重 + 有序:如统计数组中不重复的元素,并按升序输出;
- 有序集合查询:如查找某个值是否存在、找大于 / 小于某个值的第一个元素;
- 范围查询:结合
lower_bound()/upper_bound()实现区间查询(如找 [2,5) 范围内的元素); - 替代手动实现红黑树:无需自己维护平衡二叉树,直接用
set完成有序集合操作。
六、总结
| 容器类型 | 核心特点 | 适用场景 |
|---|---|---|
| set | 有序、唯一、O (logn) | 有序去重、范围查询 |
| multiset | 有序、允许重复、O (logn) | 有序可重复集合 |
| unordered_set | 无序、唯一、O (1) | 高频查找、仅需去重无需有序 |
set 是 C++ 中处理「有序唯一集合」的核心容器,掌握其特性和变体的区别,能大幅简化有序集合的开发工作,避免重复造轮子(如手动实现平衡二叉树)。