Skip to content

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 1

2. 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句话,直接选型,无需犹豫:

  1. 需要「有序+去重」→ 用 set(比如需要排序的去重场景、取最值场景);
  2. 需要「快速查找+去重」,不在乎顺序 → 用 unordered_set(比如查重、快速判断元素是否存在,效率最优);
  3. 需要「有序+允许重复」→ 用 multiset(比如统计重复元素、需要有序遍历重复数据,算法题高频);
  4. 需要「快速统计重复元素」,不在乎顺序 → 用 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容器,摆脱选型困惑,在编程中灵活运用~

百炼成钢,融会贯通