对于C++中unordered_set的详细介绍

开篇介绍:

hello 大家,那么在本篇博客中,我们将学习C++中unordered_set的使用方法,其实它的使用是和set差不多的,但是在实际中,它可能会更常用一些,所以还是需要大家去进行详细的了解的,知己知彼,方能百战百胜!!!

unordered_set - C++ Referencehttps://legacy.cplusplus.com/reference/unordered_set/unordered_set/?kw=unordered_set

这里再顺便说一下,关于unordered_multiset,大家可以自行了解一下,简单的很。

一、unordered_set 核心特性铺垫

在啃接口之前,先把 unordered_set 的核心属性搞明白,这样接口的设计逻辑就一目了然了,不用死记硬背:

  • 底层是哈希表 (哈希桶结构),增删查的平均时间复杂度 O(1),最坏情况 O(N)(哈希冲突严重时);

  • 元素无序存储,遍历结果不固定(取决于哈希函数和负载因子);

  • 自带去重特性,相同元素无法重复插入(和 set 一致);

  • 迭代器是单向迭代器(只能 ++,不支持 -- 和随机访问,比如 it+1 是非法的);

  • 迭代器本质是"常量迭代器",无法通过迭代器修改元素(会破坏哈希表结构,和 set 一致);

  • 支持桶操作(哈希表特有),可通过桶相关接口查看哈希表状态(如桶数量、负载因子等)。

二、构造函数(创建 unordered_set 容器)

核心作用:初始化 unordered_set,可指定元素、哈希函数、比较函数(默认无需手动指定),是使用它的第一步。和 set 类似,但多了哈希相关的自定义参数(一般默认即可)。

函数形式 功能说明 参数 / 返回值 适用场景 注意点 示例代码
无参构造 unordered_set<T> s; 创建空的 unordered_set,默认哈希函数和比较函数 无参数,返回空容器 后续通过 insert 插入元素 默认用 hash<T> 哈希函数、equal_to<T> 比较函数(去重) unordered_set<int> s; s.insert(3); s.insert(1);(结果无序,如 {3,1})
自定义哈希/比较 unordered_set<T, Hash, Pred> s; 按指定哈希函数和比较函数创建容器 Hash:哈希函数(默认 hash<T>);Pred:比较函数(默认 equal_to<T>) 自定义数据类型(如结构体)需手动实现哈希函数 哈希函数需保证"相同元素哈希值相同",比较函数需判断元素是否相等 // 自定义字符串哈希(示例,实际可用默认)unordered_set<string, hash<string>> s;
迭代器区间 unordered_set<T> s(first, last); 从其他容器区间 [first, last) 拷贝元素 参数:源容器迭代器区间;返回初始化后的容器 批量导入其他容器元素 自动去重,元素无序存储(和源容器顺序无关) vector<int> v={5,3,5}; unordered_set<int> s(v.begin(), v.end());(结果:{3,5},无序)
初始化列表 unordered_set<T> s({elem1,...}); 用 {} 直接初始化容器 参数:元素列表;返回初始化后的容器 创建时已知所有元素,简洁高效 支持隐式转换,自动去重 unordered_set<string> s={"banana", "apple"};(结果无序,如 {"apple", "banana"})
拷贝构造 unordered_set<T> s(other); 复制另一个 unordered_set 的元素和哈希/比较规则 参数:已存在的 unordered_set 容器;返回拷贝后的容器 复用已有容器的数据 原容器不变,拷贝后的容器元素无序(和原容器一致) unordered_set<int> s1={1,2}; unordered_set<int> s2(s1);(s2 结果:{1,2},无序)

三、迭代器函数(遍历 unordered_set 元素)

核心作用:获取容器的"遍历起点/终点",支持正向遍历(不支持反向,因为无序),是遍历元素的核心工具。重点注意:迭代器是单向的,只能 ++。

函数形式 功能说明 参数 / 返回值 适用场景 注意点 示例代码
begin()/end() begin() 指向首元素,end() 指向尾后(不指向任何元素) 返回单向迭代器 正向遍历所有元素 end() 不可解引用,循环条件为 it != s.end();迭代器仅支持 ++ unordered_set<int> s={1,2,3};for (auto it = s.begin(); it != s.end(); ++it) { cout << *it; }(输出无序,如 2 1 3)
cbegin()/cend() 只读迭代器,功能同 begin()/end() 返回常量单向迭代器 仅读取元素,不修改容器 更安全,避免误修改元素(unordered_set 本身也无法通过迭代器修改) for (auto it = s.cbegin(); it != s.cend(); ++it) { cout << *it; }
范围 for 遍历(C++11+) 语法糖,简化遍历逻辑 无参数,直接遍历容器内所有元素 无需关注迭代器,快速遍历 加 const& 避免拷贝,提升效率 for (const auto& e : s) { cout << e; }(输出无序)

四、插入函数(向 unordered_set 添加元素)

核心作用:向容器中添加元素,自动完成去重(哈希表判断相等),支持单个、批量等多种插入方式,是填充数据的核心接口。

函数形式 功能说明 参数 / 返回值 适用场景 注意点 示例代码
单个插入 pair<iterator, bool> insert(val); 插入单个元素,返回插入结果 返回 pair:- first:元素迭代器(插入成功则指向新元素,失败则指向已有元素);- second:插入成功标志(true=成功,false=重复) 插入单个元素,需要判断是否插入成功 重复元素插入失败,返回已有元素的迭代器 unordered_set<int> s={1,2};auto ret = s.insert(3); cout << ret.second;(输出 true)ret = s.insert(2); cout << ret.second;(输出 false)
位置提示 insert(pos, val); 传入提示位置插入元素(哈希表会忽略提示,仍按哈希值存储) 参数:提示迭代器 + 元素值;返回元素迭代器(无 bool 值) 大致知道元素存储位置,理论上优化效率(实际提升有限) 提示位置不影响最终存储位置,仅可能减少哈希查找次数 auto it = s.insert(s.begin(), 4); cout << *it;(输出 4)
批量插入(区间) insert(first, last); 插入其他容器区间 [first, last) 的元素 参数:源容器迭代器区间;无返回值 从其他容器批量导入元素 自动去重,元素无序存储;区间为左闭右开(不包含 last) vector<int> v={3,4,2,5}; s.insert(v.begin(), v.end());(结果去重,无序)
批量插入(列表) insert({elem1,...}); 用 {} 包裹多个元素,一次性插入 参数:初始化列表;无返回值 已知多个插入元素,简洁高效 自动去重,支持直接写元素列表 s.insert({6,7,3});(3 重复,仅插入 6、7)

五、删除函数(从 unordered_set 移除元素)

核心作用:删除容器中的元素,支持按位置、按值、按区间删除,删除后哈希表会自动调整桶结构,保证后续操作高效。

函数形式 功能说明 参数 / 返回值 适用场景 注意点 示例代码
按位置删除 iterator erase(pos); 删除迭代器指向的元素,返回下一个元素迭代器 参数:元素迭代器;返回下一个元素的迭代器 已知元素的迭代器位置(如 find 找到后删除) 不可传入 end() 迭代器(非法);需先通过 find 确认元素存在 auto it = s.find(2);if (it != s.end()) s.erase(it);(删除 2)
按值删除 size_type erase(val); 删除值为 val 的元素,返回删除的个数 参数:元素值;返回 size_t 类型(0=未找到,1=删除成功) 知道元素值,直接删除(最常用) 元素不存在时返回 0,不报错;因去重特性,最多删除 1 个 size_t del = s.erase(3); cout << del;(存在返回 1,否则 0)
按区间删除 erase(first, last); 删除区间 [first, last) 的所有元素,返回下一个元素迭代器 参数:迭代器区间;返回下一个元素的迭代器 批量删除一段连续元素(需通过迭代器定位范围) 左闭右开区间;因无序,"连续"是迭代器遍历顺序的连续,非元素值连续 auto first = s.begin(); auto last = s.find(5);if (last != s.end()) s.erase(first, last);(删除从 begin 到 5 之前的元素)
清空 clear(); 删除容器中所有元素,清空容器 无参数;无返回值 需清空整个容器,重新填充数据 清空后 size() 为 0,桶结构保留(不会删除桶) s.clear(); cout << s.empty();(输出 true)

六、查找函数(查询 unordered_set 中的元素)

核心作用:查询元素是否存在、获取元素位置,基于哈希表实现,平均效率 O(1),比 set 的 O(logN) 更快,是 unordered_set 的核心优势。

函数形式 功能说明 参数 / 返回值 适用场景 注意点 示例代码
精准查找 iterator find(val); 查找值为 val 的元素,返回其迭代器 参数:元素值;返回迭代器(找到=指向元素,未找到=end()) 确认元素存在并定位其位置 平均效率 O(1),远优于算法库的 find(O(N)) auto it = s.find(3);if (it != s.end()) cout << *it;(找到输出 3)
统计个数 size_type count(val); 统计值为 val 的元素个数 参数:元素值;返回 size_t 类型(0=不存在,1=存在) 快速判断元素是否存在(比 find 更简洁) 因去重特性,返回值只有 0 或 1;本质是通过哈希查找,效率和 find 一致 if (s.count(2)) cout << "元素 2 存在";(存在则输出)

七、容量操作(获取容器状态)

核心作用:获取容器的元素个数、判断是否为空、获取最大存储容量等,辅助我们管理容器。

函数形式 功能说明 参数 / 返回值 适用场景 注意点 示例代码
size() 返回当前容器中的元素个数 无参数;返回 size_t 类型(非负整数) 想知道容器中元素的数量 不可用于判断空容器(建议用 empty(),更高效) unordered_set<int> s={1,2,3}; cout << s.size();(输出 3)
empty() 判断容器是否为空(无元素) 无参数;返回 bool 类型(true=空,false=非空) 避免对空容器进行遍历、删除等操作(防止崩溃) 比 size() == 0 更高效(直接判断标志位,无需计算元素个数) if (!s.empty()) { /* 遍历容器 */ }
max_size() 返回理论上可存储的最大元素数 无参数;返回 size_t 类型(极大值,取决于系统和内存) 评估容器的最大存储能力(实际开发中极少用到) 仅为理论值,实际存储量受内存限制,可能小于该值 cout << s.max_size();(输出如 18446744073709551615)

八、哈希表特有操作(桶操作)

这是 unordered_set 独有的操作(set 没有),用于查看和调整哈希表的桶结构,帮助我们理解哈希表的工作原理,优化性能。

函数形式 功能说明 参数 / 返回值 适用场景 注意点 示例代码
bucket_count() 返回当前哈希表的桶数量 无参数;返回 size_t 类型 查看哈希表的桶数量,评估哈希表的填充程度 桶数量是 2 的幂次方(多数编译器实现),默认初始桶数较小(如 8、11) unordered_set<int> s={1,2,...,10}; cout << s.bucket_count();(输出如 11)
bucket(val) 返回值为 val 的元素所在的桶编号(从 0 开始) 参数:元素值;返回 size_t 类型(桶编号) 定位元素所在的桶,分析哈希冲突 元素不存在时,返回的桶编号无意义(需先通过 find 确认存在) int val=5; cout << s.bucket(val);(输出 5 所在的桶编号)
bucket_size(n) 返回第 n 个桶中的元素个数 参数:桶编号 n;返回 size_t 类型(该桶的元素个数) 查看某个桶的填充情况,判断是否存在严重哈希冲突 桶编号 n 需小于 bucket_count()(否则行为未定义) size_t n=5; cout << s.bucket_size(n);(输出桶 5 中的元素个数)
load_factor() 返回当前负载因子(元素个数 / 桶数量,浮点数) 无参数;返回 float 类型 评估哈希表的填充程度,负载因子越高,哈希冲突概率越大 负载因子超过 max_load_factor() 时,哈希表会自动扩容(rehash) cout << s.load_factor();(如 10 个元素、11 个桶,输出 ~0.909)
max_load_factor() 返回/设置最大负载因子(默认 1.0) 无参数时返回 float 类型;有参数时设置为指定值(如 0.7) 调整哈希表的扩容阈值,平衡时间和空间效率 设置更小的 max_load_factor() 可减少哈希冲突,但会增加桶数量(占用更多空间) cout << s.max_load_factor();(默认输出 1.0)s.max_load_factor(0.7f);(设置最大负载因子为 0.7)
rehash(n) 强制将桶数量设置为 n(n≥当前桶数量) 参数:目标桶数量 n;无返回值 手动扩容哈希表,避免多次自动扩容(提升效率) n 需大于等于当前桶数量;若 n 小于当前桶数量,该函数无效 s.rehash(20); cout << s.bucket_count();(输出 20)
reserve(n) 预留空间,确保桶数量足够存储 n 个元素而不触发自动扩容 参数:预计存储的元素个数 n;无返回值 批量插入大量元素前预留空间,减少自动扩容次数 会根据 max_load_factor() 计算所需桶数量(n / max_load_factor()),向上取整 s.reserve(15);(确保能存储 15 个元素而不扩容)

九、其他常用操作

函数形式 功能说明 参数 / 返回值 适用场景 注意点 示例代码
swap(unordered_set& x); 交换两个 unordered_set 的元素和哈希/比较规则 参数:另一个同类型的 unordered_set 容器;无返回值 快速交换两个容器的数据(无需手动拷贝) 时间复杂度 O(1)(仅交换底层指针,不拷贝元素) unordered_set<int> s1={1,2}, s2={3,4};s1.swap(s2);(s1 变为 {3,4},s2 变为 {1,2})

十、示例代码(实战演练)

下面用完整的代码演示 unordered_set 的核心用法,直接复制到编译器就能运行,注释详细,跟着走一遍就懂了~

cpp 复制代码
#include <iostream>
#include <algorithm>
#include <unordered_set>
#include <unordered_map>
#include <set>
#include <map>
#include <cstdio>
#include <vector>
using namespace std;

// 那么本文件我们就来使用一下哈希表
// 也就是unordered_set和unordered_map
// 其实不难发现,它们也是set和map,只不过说前面加了个unordered,意思是无序的
// 那么就是因为set和map能够通过中序遍历呈现出有序的结果,而哈希表不行
// 所以就是unorder,那么其余的其实是和基于红黑树的set和map差不多
// 甚至可以说是一模一样(夸张)
// 同样的,unordered_set存储key值
// unordered_map存储key,value,也就是pair<key,value>

// unordered_set默认要求Key支持转换为整型,注意stl库中也将string可以转换为整型
// 所以要是我们给unordered_set传入string变量的话,也没事,不用我们自己实现仿函数
// 如果不支持或者想按自己的需求走可以自行实现支持将Key转成整型的仿函数传给第二个模板参数

// unordered_set默认要求Key支持比较相等,
// 如果不支持或者想按自己的需求走可以自行实现支持将Key比较相等的仿函数传给第三个模板参数

// template < class Key, //
// unordered_set::key_type/value_type
// class Hash = hash<Key>, // unordered_set::hasher
// class Pred = equal_to<Key>, // unordered_set::key_equal
// class Alloc = allocator<Key> // unordered_set::allocator_type
// > class unordered_set;

/*
 * unordered_set与set的核心差异详解
 * 1. 使用层面的一致性
 *    查看文档可知,unordered_set支持增加、删除、查找三类核心操作,且其调用语法、接口命名、参数传递方式
 *    与set完全一致(例如插入用insert()、删除用erase()、查找用find()等)。基于这一高度一致性,
 *    关于unordered_set的具体使用方法(如函数调用、返回值处理等),此处不再赘述和演示。
 *
 * 2. 第一个核心差异:对Key类型的要求不同
 *    - set对Key的要求:set底层依赖红黑树(二叉搜索树)实现,红黑树的插入、查找、删除等操作需要通过
 *      "小于比较"来确定元素的位置,因此要求Key类型必须支持小于比较操作(内置类型int/string默认支持,
 *      自定义类型需重载operator<运算符)。
 *    - unordered_set对Key的要求:unordered_set底层基于哈希表实现,哈希表的核心是通过哈希函数将Key转换为
 *      整形哈希值(确定存储位置),同时处理哈希冲突时需要判断两个Key是否相等,因此要求Key满足两点:
 *      ① 支持通过哈希函数转换为整形(内置类型默认支持,自定义类型需自定义哈希函数);
 *      ② 支持等于比较操作(自定义类型需重载operator==运算符)。
 *    - 补充说明:unordered_set对Key的这两点要求并非自身额外限制,而是哈希表底层实现的本质要求,
 *      需后续结合哈希表底层逻辑才能真正理解。
 *
 * 3. 第二个核心差异:迭代器特性与遍历结果不同
 *    该差异包含"迭代器类型"和"遍历特性"两个维度:
 *    - 迭代器类型差异:set的iterator属于双向迭代器,支持"++"(向后遍历)和"--"(向前遍历)操作;
 *      unordered_set的迭代器仅为单向迭代器,仅支持"++"向后遍历,不支持"--"向前遍历。
 *    - 遍历结果特性差异:
 *      ① set:底层是红黑树(有序二叉搜索树),元素按Key大小排序存储,迭代器遍历本质是红黑树的中序遍历,
 *         因此遍历结果呈现"有序+去重"的特点(无重复元素,且按Key大小有序);
 *      ② unordered_set:底层是哈希表,元素存储位置由哈希值决定(与Key大小、插入顺序均无关),
 *         因此迭代器遍历结果呈现"无序+去重"的特点(无重复元素,但遍历顺序无规律)。
 *      二者都可以去重哦!!!
 *
 * 4. 第三个核心差异:增删查改的性能表现不同
 *    - 整体性能趋势:在绝大多数业务场景下(无极端哈希冲突、数据量较大),unordered_set的增删查改效率优于set。
 *    - 底层效率支撑:
 *      ① set(红黑树):增删查改操作需要维护树的平衡,时间复杂度为O(logN)(N为元素个数);
 *      ② unordered_set(哈希表):无哈希冲突的理想场景下,增删查操作平均时间复杂度为O(1)(常数级),
 *         即使存在少量哈希冲突,效率也远高于红黑树。
 *    - 验证方式:二者具体的性能差异(如操作耗时、执行次数对比),可参考下方代码的实际运行演示结果。
 */
using namespace std;
int test_set2()
{
    const size_t N = 1000000;
    unordered_set<int> us;
    set<int> s;
    vector<int> v;
    v.reserve(N);
    srand(time(0));
    for (size_t i = 0; i < N; ++i)
    {
        // v.push_back(rand()); // N比较大时,重复值比较多
        v.push_back(rand() + i); // 重复值相对少
        // v.push_back(i); // 没有重复,有序
    }
    // 21:15
    size_t begin1 = clock();
    for (auto e : v)
    {
        s.insert(e);
    }
    size_t end1 = clock();
    cout << "set insert:" << end1 - begin1 << endl;
    size_t begin2 = clock();
    us.reserve(N);
    for (auto e : v)
    {
        us.insert(e);
    }
    size_t end2 = clock();
    cout << "unordered_set insert:" << end2 - begin2 << endl;
    int m1 = 0;
    size_t begin3 = clock();
    for (auto e : v)
    {
        auto ret = s.find(e);
        if (ret != s.end())
        {
            ++m1;
        }
    }
    size_t end3 = clock();
    cout << "set find:" << end3 - begin3 << "->" << m1 << endl;
    int m2 = 0;
    size_t begin4 = clock();
    for (auto e : v)
    {
        auto ret = us.find(e);
        if (ret != us.end())
        {
            ++m2;
        }
    }
    size_t end4 = clock();
    cout << "unorered_set find:" << end4 - begin4 << "->" << m2 << endl;
    cout << "插入数据个数:" << s.size() << endl;
    cout << "插入数据个数:" << us.size() << endl
         << endl;
    size_t begin5 = clock();
    for (auto e : v)
    {
        s.erase(e);
    }
    size_t end5 = clock();
    cout << "set erase:" << end5 - begin5 << endl;
    size_t begin6 = clock();
    for (auto e : v)
    {
        us.erase(e);
    }
    size_t end6 = clock();
    cout << "unordered_set erase:" << end6 - begin6 << endl
         << endl;
    return 0;
}
int main()
{
    test_set2();
    return 0;
}

// 它们的使用可以说是和set和map一样,所以下面就简单示例一下
// 简单示例unorder_map,至于另一个,我们放在另一个文件

int main() {
    /************************ 一、构造函数:创建unordered_set对象 ************************/
    cout << "===== 1. 构造函数示例 =====" << endl;

    // 1. 无参构造:创建空的unordered_set,底层哈希表默认桶数(不同编译器可能不同,如VS默认8)
    // 特性:存储int类型,默认哈希函数hash<int>(),默认等于比较equal_to<int>(),去重+无序
    unordered_set<int> us1;
    cout << "us1(无参构造)是否为空:" << boolalpha << us1.empty() << endl; // boolalpha让bool输出true/false

    // 2. 迭代器区间构造:从其他容器的迭代器区间初始化
    // 步骤:先创建vector,再用vector的[begin, end)区间构造unordered_set
    vector<int> vec = {10, 20, 30, 20, 40}; // 包含重复元素20
    unordered_set<int> us2(vec.begin(), vec.end());
    // 特性:自动去重(20只保留一个),遍历无序(输出顺序与vec无关)
    cout << "us2(迭代器构造)元素:";
    for (auto e : us2) 
    cout << e << " "; // 示例输出:10 20 30 40(顺序可能不同)
    
    cout << endl;

    // 3. 初始化列表构造(C++11+):最常用的构造方式,直接传{}包裹的元素
    // 特性:支持任意可哈希类型(如int/string),自动去重
    unordered_set<int> us3 = {1, 2, 3, 2, 4}; // 重复元素2被去重
    cout << "us3(初始化列表构造)元素:";
    for (auto e : us3) 
    cout << e << " "; // 示例输出:1 2 3 4(顺序随机)
    
    cout << endl;

    // 4. 拷贝构造:复制已有unordered_set的所有元素
    unordered_set<int> us4(us3); // 完全复制us3的元素、哈希表结构
    cout << "us4(拷贝构造)元素个数:" << us4.size() << endl; // 输出4

    // 5. 移动构造(C++11+):接管已有unordered_set的资源(us5变为空)
    // 语法:std::move将左值转为右值,避免拷贝,效率更高
    unordered_set<int> us5(move(us3));
    cout << "移动构造后,原us3是否为空:" << us3.empty() << endl; // 输出true(us3资源被移走)
    cout << "us5(移动构造)元素:";
    for (auto e : us5) 
    cout << e << " "; // 输出原us3的元素
    
    cout << endl;

    // 6. 自定义桶数构造:指定初始桶的数量(减少后续rehash次数)
    // 语法:unordered_set<T> us(n),n为初始桶数
    unordered_set<string> us6(10); // 初始桶数10,存储string类型
    us6.insert("apple");
    cout << "us6(指定桶数构造)的桶数量:" << us6.bucket_count() << endl; // 输出10(未触发rehash)

    /************************ 二、插入操作:insert接口 ************************/
    cout << "\n===== 2. 插入操作(insert)示例 =====" << endl;

    unordered_set<int> insert_us;

    // 1. 单元素插入:insert(val),返回值为pair<iterator, bool>
    // 返回值解析:
    // - pair.first:指向插入/已存在元素的迭代器(去重特性:已存在则返回已有元素迭代器)
    // - pair.second:bool值,true=插入成功(元素不存在),false=插入失败(元素已存在)
    auto ret1 = insert_us.insert(10); // 插入新元素10
    cout << "插入10是否成功:" << ret1.second << ",插入位置的元素:" << *ret1.first << endl; // true 10

    auto ret2 = insert_us.insert(10); // 插入重复元素10
    cout << "插入重复10是否成功:" << ret2.second << ",返回的迭代器指向:" << *ret2.first << endl; // false 10

    // 2. 指定迭代器插入:insert(hint, val)
    // 参数解析:
    // - hint:迭代器,仅作为"提示位置"(哈希表会忽略,仍按哈希值存储)
    // 返回值:指向插入/已存在元素的迭代器(无bool值)
    auto it_hint = insert_us.insert(insert_us.begin(), 20); // 提示在begin位置插入20
    cout << "指定迭代器插入20,返回迭代器指向:" << *it_hint << endl; // 20

    // 3. 区间插入:insert(first, last)
    // 作用:将[first, last)区间内的所有元素插入,自动去重,无返回值
    vector<int> insert_vec = {30, 40, 20, 50}; // 20是重复元素
    insert_us.insert(insert_vec.begin(), insert_vec.end());
    cout << "区间插入后,insert_us元素:";
    for (auto e : insert_us) 
    cout << e << " "; // 示例:10 20 30 40 50(无序)
    
    cout << endl;

    // 4. 初始化列表插入(C++11+):insert({val1, val2, ...})
    // 作用:批量插入多个元素,自动去重,无返回值
    insert_us.insert({60, 70, 30}); // 30重复
    cout << "初始化列表插入后,insert_us元素:";
    for (auto e : insert_us) 
    cout << e << " "; // 示例:10 20 30 40 50 60 70(无序)
    
    cout << endl;

    /************************ 三、删除操作:erase接口 ************************/
    cout << "\n===== 3. 删除操作(erase)示例 =====" << endl;

    unordered_set<int> erase_us = {10, 20, 30, 40, 50};
    cout << "删除前,erase_us元素:";
    for (auto e : erase_us) cout << e << " ";
    cout << endl;

    // 1. 按迭代器删除:erase(pos)
    // 参数:指向要删除元素的迭代器,无返回值
    // 注意:unordered_set是单向迭代器,仅支持++,不支持--/随机访问
    auto it_erase = erase_us.find(20); // 先找到20的迭代器
    if (it_erase != erase_us.end()) 
    { // 必须判断迭代器是否有效(避免删除end())
        erase_us.erase(it_erase);
        cout << "按迭代器删除20后,erase_us元素:";
        for (auto e : erase_us) cout << e << " "; // 无20
        cout << endl;
    }

    // 2. 按key删除:erase(val)
    // 返回值:size_t类型,删除的元素个数(unordered_set去重,故返回0或1)
    size_t del_count1 = erase_us.erase(30); // 删除存在的30
    cout << "按key删除30,删除个数:" << del_count1 << endl; // 1

    size_t del_count2 = erase_us.erase(100); // 删除不存在的100
    cout << "按key删除100,删除个数:" << del_count2 << endl; // 0

    // 3. 区间删除:erase(first, last)
    // 作用:删除[first, last)区间内的所有元素,无返回值
    // 步骤:先找到起始和结束迭代器(示例:删除从begin到find(50)的区间)
    auto it_begin = erase_us.begin();
    auto it_end = erase_us.find(50);
    if (it_end != erase_us.end()) 
    {
        erase_us.erase(it_begin, it_end);
        cout << "区间删除后,erase_us剩余元素:";
        for (auto e : erase_us) cout << e << " "; // 仅剩余50
        cout << endl;
    }

    /************************ 四、查找操作:find/count ************************/
    cout << "\n===== 4. 查找操作(find/count)示例 =====" << endl;

    unordered_set<int> find_us = {10, 20, 30, 40, 50};

    // 1. find接口:find(val)
    // 作用:查找key为val的元素,返回迭代器
    // 返回值:找到=指向该元素的迭代器;未找到=end()迭代器
    auto it_find = find_us.find(30);
    if (it_find != find_us.end()) 
    {
        cout << "find(30)找到元素:" << *it_find << endl; // 30
    } 
    else 
    {
        cout << "find(30)未找到元素" << endl;
    }

    auto it_find_none = find_us.find(100);
    if (it_find_none == find_us.end()) 
    {
        cout << "find(100)未找到元素" << endl;
    }

    // 2. count接口:count(val)
    // 作用:统计key为val的元素个数(unordered_set去重,故返回0或1)
    // 常用场景:快速判断元素是否存在(比find更简洁)
    size_t count_20 = find_us.count(20);
    cout << "count(20)结果:" << count_20 << " → 说明元素存在" << endl; // 1

    size_t count_100 = find_us.count(100);
    cout << "count(100)结果:" << count_100 << " → 说明元素不存在" << endl; // 0

    /************************ 五、迭代器遍历 ************************/
    cout << "\n===== 5. 迭代器遍历示例 =====" << endl;

    unordered_set<string> iter_us = {"apple", "banana", "orange", "grape"};

    // 1. 普通迭代器遍历(单向迭代器,仅支持++)
    cout << "普通迭代器遍历:";
    unordered_set<string>::iterator it_iter;
    for (it_iter = iter_us.begin(); it_iter != iter_us.end(); ++it_iter) 
    {
        cout << *it_iter << " "; // 输出无序,如apple grape banana orange
    }
    cout << endl;

    // 2. const迭代器遍历:只读,不可修改元素(unordered_set的key本身不可修改)
    cout << "const迭代器遍历:";
    unordered_set<string>::const_iterator it_const;
    for (it_const = iter_us.cbegin(); it_const != iter_us.cend(); ++it_const) 
    {
        cout << *it_const << " "; // 与普通迭代器结果一致
    }
    cout << endl;

    // 4. 范围for遍历(C++11+):最简洁的遍历方式
    cout << "范围for遍历:";
    for (const auto& e : iter_us) 
    { // 加const&避免拷贝,提升效率
        cout << e << " ";
    }
    cout << endl;

    /************************ 六、容量操作:size/empty/max_size ************************/
    cout << "\n===== 6. 容量操作示例 =====" << endl;

    unordered_set<int> capacity_us = {10, 20, 30};

    // 1. size():返回当前元素个数
    cout << "capacity_us元素个数(size):" << capacity_us.size() << endl; // 3

    // 2. empty():判断是否为空(size==0),返回bool
    cout << "capacity_us是否为空(empty):" << boolalpha << capacity_us.empty() << endl; // false

    // 3. max_size():返回理论上可存储的最大元素数(系统/内存限制)
    cout << "capacity_us最大可存储元素数(max_size):" << capacity_us.max_size() << endl; // 大数,如18446744073709551615

    // 4. clear():清空所有元素,size变为0,桶结构保留
    capacity_us.clear();
    cout << "clear后,capacity_us是否为空:" << capacity_us.empty() << endl; // true
    cout << "clear后,capacity_us元素个数:" << capacity_us.size() << endl; // 0

    /************************ 七、哈希表特有操作(桶操作) ************************/
    cout << "\n===== 7. 哈希表桶操作示例(unordered_set特有) =====" << endl;

    unordered_set<int> bucket_us = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    // 1. bucket_count():返回当前哈希表的桶数量
    cout << "当前桶数量(bucket_count):" << bucket_us.bucket_count() << endl; // 示例:11(编译器默认)

    // 2. bucket(val):返回key=val的元素所在的桶编号(从0开始)
    int key = 5;
    size_t bucket_num = bucket_us.bucket(key);
    cout << "元素" << key << "所在桶编号:" << bucket_num << endl; // 示例:5

    // 3. bucket_size(n):返回第n个桶中的元素个数
    size_t bucket_size_num = bucket_us.bucket_size(bucket_num);
    cout << "桶" << bucket_num << "中的元素个数:" << bucket_size_num << endl; // 示例:1(无哈希冲突)

    // 4. load_factor():返回当前负载因子(size / bucket_count,浮点数)
    // 负载因子:衡量哈希表的填充程度,超过max_load_factor会触发rehash
    float load_fac = bucket_us.load_factor();
    cout << "当前负载因子(load_factor):" << load_fac << endl; // 示例:10/11≈0.909

    // 5. max_load_factor():返回/设置最大负载因子(默认1.0)
    float max_load_fac = bucket_us.max_load_factor();
    cout << "默认最大负载因子(max_load_factor):" << max_load_fac << endl; // 1.0

    // 设置最大负载因子为0.7(超过则自动rehash,增加桶数)
    bucket_us.max_load_factor(0.7f);
    cout << "设置后最大负载因子:" << bucket_us.max_load_factor() << endl; // 0.7

    // 6. rehash(n):强制设置桶数量为n(n≥当前bucket_count)
    bucket_us.rehash(20); // 桶数改为20
    cout << "rehash(20)后,桶数量:" << bucket_us.bucket_count() << endl; // 20

    // 7. reserve(n):预留空间,确保桶数足够存储n个元素而不触发rehash
    // 区别于rehash:reserve根据负载因子计算所需桶数,rehash直接指定桶数
    bucket_us.reserve(15);
    cout << "reserve(15)后,桶数量:" << bucket_us.bucket_count() << endl; // 示例:22(15/0.7≈21.4,向上取整)

    /************************ 八、其他操作:swap ************************/
    cout << "\n===== 8. 交换操作(swap)示例 =====" << endl;

    unordered_set<int> swap_us1 = {1, 2, 3};
    unordered_set<int> swap_us2 = {10, 20, 30, 40};

    cout << "交换前:" << endl;
    
    cout << "swap_us1元素:";
    for (auto e : swap_us1) 
    cout << e << " "; // 1 2 3
    
    cout << endl;
    
    cout << "swap_us2元素:";
    for (auto e : swap_us2) 
    cout << e << " "; // 10 20 30 40
    
    cout << endl;

    // swap(us1, us2):交换两个unordered_set的内容,时间复杂度O(1)(仅交换底层指针)
    swap_us1.swap(swap_us2);

    cout << "交换后:" << endl;
    cout << "swap_us1元素:";
    for (auto e : swap_us1) 
    cout << e << " "; // 10 20 30 40
    
    cout << endl;
    
    cout << "swap_us2元素:";
    for (auto e : swap_us2) 
    cout << e << " "; // 1 2 3
    
    cout << endl;

    return 0;
}

//那么还有unordered_multiset,使用方法和unordered_set一样,这里就不再多余赘述!!!

十一、核心总结

unordered_set 的接口设计围绕"无序、高效、去重"展开,核心亮点是哈希表带来的 O(1) 平均增删查效率,和 set 的"有序、O(logN) 效率"形成互补,根据场景选择即可:

1. 核心高频接口

  • 构造:初始化列表构造(unordered_set<int> s={1,2,3};),最简洁;

  • 遍历:范围 for 遍历(for (const auto& e : s)),无需关注迭代器;

  • 插入:insert(val)(单个插入,返回 pair 判断成功)、insert({elem1,...})(批量插入);

  • 删除:erase(val)(按值删除,返回删除个数)、clear()(清空);

  • 查找:find(val)(精准定位)、count(val)(快速判断存在);

  • 桶操作:reserve(n)(批量插入前预留空间,优化效率)。

2. set vs unordered_set 选型建议

场景需求 选 set 选 unordered_set
元素需要有序
增删查效率优先(大数据量) ❌(O(logN)) ✅(O(1) 平均)
需要范围查找(如 lower_bound) ❌(无序,无该接口)
内存占用敏感 ✅(红黑树内存开销小) ❌(哈希表需预留桶空间)

3. 注意事项

  • 迭代器是单向的,仅支持 ++,不支持 -- 和随机访问(如 it+1 非法);

  • 元素无序,遍历结果不固定(取决于哈希函数);

  • 自定义数据类型(如结构体)需手动实现哈希函数和比较函数;

  • 批量插入大量元素前,用 reserve(n) 预留空间,减少自动扩容次数,提升效率。

结语:以高效为刃,破无序场景之局

哈喽各位小伙伴,咱们关于 unordered_set 的学习到这里就圆满结束啦!回顾一下,咱们从底层哈希表的逻辑出发,吃透了它"无序但高效"的核心特性,拆解了构造、迭代器、增删查、桶操作等核心接口,还通过示例代码实战了用法,最后明确了和 set 的选型边界------这就是技术学习的"从底层到上层"的思路,搞懂了底层逻辑,接口用法自然不用死记硬背。

unordered_set 最大的价值,就是在"无需有序"的场景下,用 O(1) 的平均效率帮我们快速解决问题。比如统计不重复数据、判断元素是否存在、快速增删元素这些场景,它比 set 更高效,是我们开发中的"效率利器"。而桶操作相关的接口,虽然平时用得不多,但理解它们能帮我们更深入地掌握哈希表的工作原理,比如负载因子、扩容机制,这些知识在后续学习哈希表、分布式缓存等内容时都会用到,是技术积累的"复利"。

可能有小伙伴会问,"那 unordered_set 有对应的'允许重复'版本吗?" 答案是有的!就是 unordered_multiset,它和 unordered_set 的关系,就像 multiset 和 set 的关系------核心差异是允许重复元素,其他接口用法几乎一致(比如 insert 不返回 bool,erase 按值删除会删除所有重复元素)。如果后续有需求,咱们再专门聊它,现在先把 unordered_set 吃透就好~

技术学习从来不是"学完就忘",而是要"融会贯通"。咱们今天学的 unordered_set,和之前学的 set、vector、list 等容器,共同构成了 C++ STL 的容器体系。不同的容器有不同的底层实现和适用场景,学会根据需求选择合适的容器,是从"会写代码"到"会写好代码"的关键一步。比如处理"去重+有序"用 set,"去重+高效"用 unordered_set,"随机访问+连续内存"用 vector,"频繁插入删除"用 list------这些选型逻辑,需要我们在实践中不断强化。

最后,还是要鼓励大家多动手实践。示例代码里的每个函数,都可以复制到编译器里运行一遍,修改一下参数(比如插入重复元素、删除不存在的元素),看看输出结果的变化,这样才能真正理解接口的用法。遇到疑惑也不用怕,比如"为什么迭代器只能 ++?""负载因子怎么影响效率?",多查文档、多调试,慢慢就能豁然开朗。

相关推荐
燃于AC之乐2 小时前
深入解剖STL RB-tree(红黑树):用图解带入相关复杂操作实现
开发语言·c++·stl·红黑树·大厂面试·图解·插入操作
吃着火锅x唱着歌2 小时前
LeetCode 456.132模式
数据结构·算法·leetcode
二木九森2 小时前
LeetCode-寻找环形链表的入口
算法·leetcode·链表
iPadiPhone2 小时前
性能优化的“双刃剑”:MySQL 查询缓存深度架构解析与面试复盘
java·后端·mysql·缓存·面试·性能优化
进击切图仔2 小时前
linux 上编译 c++ 项目结构
linux·运维·c++
天涯学馆2 小时前
从 V8 引擎看 JS 代码是如何一步步变成机器指令的
前端·javascript·面试
艾莉丝努力练剑2 小时前
C语言中&的多重用途解析
运维·服务器·c语言·c++·人工智能
菜菜小狗的学习笔记2 小时前
数据结构(三)哈希表
数据结构·散列表
飞Link2 小时前
耳机连接电脑时调节耳机音量电脑音量也会随着改变
算法·电脑