Appearance
set系列容器详解
set系列容器是STL关联式容器的核心分支,以“元素值”为关键字,核心作用是实现高效的查找、去重,部分类型支持重复元素存储,底层实现决定了其有序/无序特性和效率,是日常开发和算法题中的高频工具。
在C++ STL(标准模板库)中,set系列容器是高频使用的集合类工具,核心作用是存储数据、实现快速查找与去重(部分类型支持重复)。很多初学者容易混淆set、unordered_set、multiset、unordered_multiset这四种容器,本文将从定义、特性、常用操作、适用场景四个维度,帮你彻底搞懂它们的区别与用法,结合代码示例,让你看完就能上手。
四种set容器核心特性对比
| 容器类型 | 是否有序 | 是否允许重复 | 底层实现 | 核心效率(插入/查找/删除) | 核心优势 |
|---|---|---|---|---|---|
| set | ✅ 是(默认升序) | ❌ 否 | 红黑树(平衡二叉搜索树) | O(log n)(稳定) | 有序+去重,支持快速取最值 |
| unordered_set | ❌ 否 | ❌ 否 | 哈希表 | O(1)(平均),最坏O(n) | 查找速度极快,适合纯去重、快速查询 |
| multiset | ✅ 是(默认升序) | ✅ 是 | 红黑树 | O(log n)(稳定) | 有序+允许重复,支持统计重复元素个数 |
| unordered_multiset | ❌ 否 | ✅ 是 | 哈希表 | O(1)(平均),最坏O(n) | 快速统计重复元素,无需排序 |
逐一详解:定义与基础用法
所有set容器的使用,需包含对应头文件,核心操作高度统一,仅细节有差异,以下代码均包含完整头文件和命名空间,可直接复制运行。
1. set:有序、不可重复集合(最基础)
set是最常用的set容器,核心作用是「有序去重」,数据插入后会自动按升序排列,且同一个元素只能存储一次。
cpp
#include <iostream>
#include <set> // set的头文件
using namespace std;
int main() {
// 1. 定义:默认升序,存储int类型
set<int> s;
// 2. 插入元素(重复插入会自动忽略)
s.insert(3);
s.insert(1);
s.insert(2);
s.insert(1); // 重复元素,插入无效
// 3. 遍历(自动升序)
cout << "set遍历(升序):";
for (auto x : s) { // 范围for遍历(C++11及以上)
cout << x << " ";
}
cout << endl; // 输出:1 2 3
// 4. 查找元素
if (s.count(2)) { // count():存在返回1,不存在返回0
cout << "元素2存在" << endl;
}
// 5. 取最值(利用有序特性)
cout << "set最小值:" << *s.begin() << endl; // begin()指向最小元素
cout << "set最大值:" << *s.rbegin() << endl; // rbegin()指向最大元素
// 6. 删除元素
s.erase(2); // 删除值为2的元素
cout << "删除2后遍历:";
for (auto x : s) {
cout << x << " ";
}
cout << endl; // 输出:1 3
// 7. 其他常用操作
cout << "元素个数:" << s.size() << endl; // 输出:2
if (s.empty()) {
cout << "集合为空" << endl;
} else {
cout << "集合非空" << endl;
}
s.clear(); // 清空集合
return 0;
}补充:降序排列
若需要「降序排列」,定义时可指定排序规则:
cpp
set<int, greater<int>> s; // 降序排列
s.insert(3);
s.insert(1);
s.insert(2);
// 遍历输出:3 2 12. unordered_set:无序、不可重复集合(最快查找)
unordered_set与set的唯一区别是「无序」,底层基于哈希表实现,查找、插入、删除的平均效率远高于set,但代价是不保证元素顺序,且占用更多内存。
cpp
#include<iostream>
#include <unordered_set> // unordered_set的头文件
using namespace std;
int main() {
// 1. 定义:无序,存储string类型
unordered_set<string> us;
// 2. 插入元素(重复无效)
us.insert("apple");
us.insert("banana");
us.insert("orange");
us.insert("apple"); // 重复插入,无效
// 3. 遍历(无序,每次运行顺序可能不同)
cout << "unordered_set遍历(无序):";
for (auto str : us) {
cout << str << " ";
}
cout << endl; // 示例输出:banana apple orange(顺序不固定)
// 4. 查找元素(效率比set高)
if (us.find("orange") != us.end()) { // find()返回迭代器,找不到返回end()
cout << "元素orange存在" << endl;
}
// 5. 删除元素
us.erase("banana"); // 删除值为banana的元素
// 6. 其他常用操作(与set一致)
cout << "元素个数:" << us.size() << endl; // 输出:2
us.clear(); // 清空
return 0;
}关键提醒
unordered_set不支持排序相关操作(如取最值),因为其元素是无序的,若需要排序,需先将元素存入vector再排序。
3. multiset:有序、可重复集合(统计重复)
multiset与set的核心区别是「允许重复元素」,底层同样是红黑树,插入后自动排序,支持统计重复元素的个数,是算法题中常用的“有序可重复”容器(可替代优先队列的部分场景)。
cpp
#include <iostream>
#include <set> // multiset与set共用头文件
using namespace std;
int main() {
// 1. 定义:有序,允许重复,存储int类型
multiset<int> ms;
// 2. 插入元素(允许重复)
ms.insert(5);
ms.insert(5);
ms.insert(3);
ms.insert(5);
ms.insert(2);
// 3. 遍历(自动升序,保留重复元素)
cout << "multiset遍历:";
for (auto x : ms) {
cout << x << " ";
}
cout << endl; // 输出:2 3 5 5 5
// 4. 统计重复元素个数
cout << "元素5的重复次数:" << ms.count(5) << endl; // 输出:3
// 5. 删除元素(重点!容易踩坑)
ms.erase(5); // 注意:删除所有值为5的元素
cout << "删除所有5后遍历:";
for (auto x : ms) {
cout << x << " ";
}
cout << endl; // 输出:2 3
// 重新插入5,演示“只删除一个5”
ms.insert(5);
ms.insert(5);
ms.erase(ms.find(5)); // 用迭代器删除,只删一个5
cout << "只删除一个5后遍历:";
for (auto x : ms) {
cout << x << " ";
}
cout << endl; // 输出:2 3 5
// 6. 取最值(与set一致,利用有序特性)
cout << "multiset最小值:" << *ms.begin() << endl; // 输出:2
return 0;
}踩坑提醒
multiset的erase()有两种用法——删除值时,会删除所有该值的元素;删除迭代器时,只删除迭代器指向的那一个元素,这是最容易出错的点。
4. unordered_multiset:无序、可重复集合(快速统计)
unordered_multiset与multiset的区别是「无序」,底层基于哈希表实现,允许重复元素,查找、插入的平均效率极高,适合不需要排序、只需要快速统计重复元素的场景。
cpp
#include <iostream>
#include <unordered_set> // 与unordered_set共用头文件
using namespace std;
int main() {
// 1. 定义:无序,允许重复,存储int类型
unordered_multiset<int> ums;
// 2. 插入元素(允许重复)
ums.insert(2);
ums.insert(2);
ums.insert(7);
ums.insert(2);
ums.insert(9);
// 3. 遍历(无序,保留重复元素)
cout << "unordered_multiset遍历:";
for (auto x : ums) {
cout << x << " ";
}
cout << endl; // 示例输出:2 2 2 7 9(顺序不固定)
// 4. 统计重复元素个数(与multiset一致)
cout << "元素2的重复次数:" << ums.count(2) << endl; // 输出:3
// 5. 删除元素(与multiset一致,注意坑点)
ums.erase(2); // 删除所有值为2的元素
cout << "删除所有2后遍历:";
for (auto x : ums) {
cout << x << " ";
}
cout << endl; // 输出:7 9
// 6. 其他常用操作(与unordered_set一致)
cout << "元素个数:" << ums.size() << endl; // 输出:2
ums.clear(); // 清空
return 0;
}常用操作汇总(四种容器通用+差异)
四种set容器的常用操作高度统一,以下汇总核心操作,重点标注差异点,方便快速查阅。
3.1 通用操作(四种容器都支持)
| 操作 | 语法 | 说明 |
|---|---|---|
| 插入 | insert(元素) | 插入一个元素,set/unordered_set重复插入无效;multiset/ums允许重复插入 |
| 删除(按值) | erase(元素) | 删除所有值为该元素的节点(multiset/ums需注意,会删全部) |
| 删除(按迭代器) | erase(迭代器) | 删除迭代器指向的单个元素,四种容器均适用 |
| 查找 | find(元素) | 返回指向该元素的迭代器,找不到返回end() |
| 统计个数 | count(元素) | 返回该元素的个数,set/unordered_set只能返回0或1 |
| 清空 | clear() | 清空所有元素,容器变为空 |
| 判空 | empty() | 为空返回true,否则返回false |
| 获取大小 | size() | 返回容器中元素的个数 |
3.2 差异操作(仅部分容器支持)
- 「取最值」:仅set、multiset支持(因为有序),用
*(begin())取最小值,*(rbegin())取最大值;unordered_set、unordered_multiset不支持。 - 「排序」:仅set、multiset支持自动排序,unordered系列容器无序,无法直接排序。
- 「自定义排序」:仅set、multiset支持,定义时指定排序规则(如降序);unordered系列容器不支持。
适用场景总结(一句话选型)
很多初学者纠结“该用哪种set”,记住以下4句话,直接选型,无需犹豫:
- 需要「有序+去重」→ 用 set(比如需要排序的去重场景、取最值场景);
- 需要「快速查找+去重」,不在乎顺序 → 用 unordered_set(比如查重、快速判断元素是否存在,效率最优);
- 需要「有序+允许重复」→ 用 multiset(比如统计重复元素、需要有序遍历重复数据,算法题高频);
- 需要「快速统计重复元素」,不在乎顺序 → 用 unordered_multiset(比如统计一个数组中每个元素的出现次数,效率比multiset高)。
常见误区与注意事项
5.1 误区1:混淆erase()的用法
对于multiset和unordered_multiset,erase(元素) 会删除所有该值的元素,而不是一个!如果只想删除一个,必须用迭代器(如 erase(find(元素)))。
5.2 误区2:认为unordered系列容器效率一定更高
unordered系列的平均效率是O(1),但最坏情况(哈希冲突严重)会退化到O(n);而set、multiset的效率稳定在O(log n),适合数据量较大、哈希冲突可能严重的场景。
5.3 误区3:试图修改set容器中的元素
所有set容器的元素都是「不可修改」的!因为修改元素会破坏其底层排序(红黑树)或哈希结构(哈希表)。如果需要修改元素,只能先删除旧元素,再插入新元素。
5.4 注意事项:头文件区分
- set、multiset:包含
<set>头文件; - unordered_set、unordered_multiset:包含
<unordered_set>头文件。
实战案例:四种容器对比演示
以下代码将四种容器放在一起,插入相同的元素,演示它们的遍历顺序、重复处理、查找效率差异,帮助你直观感受区别(可直接复制运行)。
cpp
#include <iostream>
#include <set>
#include <unordered_set>
using namespace std;
int main() {
// 定义四种容器
set<int> s;
unordered_set<int> us;
multiset<int> ms;
unordered_multiset<int> ums;
// 插入相同的元素
int arr[] = {3, 1, 2, 3, 4, 1, 5};
for (int x : arr) {
s.insert(x);
us.insert(x);
ms.insert(x);
ums.insert(x);
}
// 遍历对比
cout << "1. set遍历(有序、去重):";
for (auto x : s) cout << x << " ";
cout << endl;
cout << "2. unordered_set遍历(无序、去重):";
for (auto x : us) cout << x << " ";
cout << endl;
cout << "3. multiset遍历(有序、可重复):";
for (auto x : ms) cout << x << " ";
cout << endl;
cout << "4. unordered_multiset遍历(无序、可重复):";
for (auto x : ums) cout << x << " ";
cout << endl;
// 查找对比(以元素3为例)
cout << "\n元素3的个数:" << endl;
cout << "set:" << s.count(3) << endl;
cout << "unordered_set:" << us.count(3) << endl;
cout << "multiset:" << ms.count(3) << endl;
cout << "unordered_multiset:" << ums.count(3) << endl;
return 0;
}运行结果示例:
1. set遍历(有序、去重):1 2 3 4 5
2. unordered_set遍历(无序、去重):3 1 2 4 5
3. multiset遍历(有序、可重复):1 1 2 3 3 4 5
4. unordered_multiset遍历(无序、可重复):3 3 1 1 2 4 5
元素3的个数:
set:1
unordered_set:1
multiset:2
unordered_multiset:2总结
set系列容器的核心价值的是「高效的查找、去重、排序」,四种容器的差异本质是「有序与否」和「允许重复与否」,底层实现决定了它们的效率和适用场景。
掌握它们的关键的是:记住特性对比表,明确自己的需求(是否需要有序、是否允许重复、是否追求极致查找效率),再对应选型。日常开发中,set和unordered_set使用频率最高;算法题中,multiset是高频工具,常用于处理有序重复数据。
希望本文能帮你彻底搞懂四种set容器,摆脱选型困惑,在编程中灵活运用~
