新闻详情

新闻详情

首页 / 资讯中心 / 详情

【C++进阶】unordered_set 和unordered_map 深度解析

发布时间:2026/9/30 9:26:52来源:尧图网络
【C++进阶】unordered_set 和unordered_map 深度解析
目录一、基础概念二、模板参数完整拆解unordered_set 模板原型unordered_map 模板原型三、底层开链哈希桶拉链法哈希冲突负载因子 load_factorrehash 重哈希机制【面试高频】四、迭代器核心特性五、key 的约束条件面试高频map /set红黑树unordered_set /unordered_map哈希六、unordered_multiset /unordered_multimap七、map/set 和 unordered_map/unordered_set 选型策略✅优先选择 unordered哈希✅优先选择 map /set红黑树八、完整性能测试参考代码九、大厂面试高频问题总结Q1map 与 unordered_map 底层时间复杂度Q2unordered 为什么既需要 hash 函数又需要 运算符Q3rehash 重哈希做了什么会带来什么问题Q4unordered 的迭代器为什么不支持 -- 反向迭代Q5multimap 和 unordered_multimap 的区别Q6什么时候用 map什么时候用 unordered_mapQ7自定义结构体作为 unordered_map 的 key需要实现什么十、开发易错坑清单一、基础概念unordered_set、unordered_map属于 C STL 关联式容器。unordered_set存储 keykey 唯一unordered_map存储pairconst Key,T键值对key 唯一与之对应的红黑树容器set、map。 二者对外 API 接口高度趋同都支持insert、find、erase但是底层数据结构完全不同带来有序性、时间复杂度、迭代器行为上巨大差异。核心区分set/map底层红黑树平衡二叉搜索树遍历输出 key 有序增删查稳定 \(O(logN)\)。unordered_set/unordered_map底层开链法哈希桶遍历输出无序增删查平均 \(O(1)\)最坏 \(O(N)\)。头文件#include unordered_set #include unordered_map表格容器底层是否有序key 是否唯一迭代器类别时间复杂度key 要求set红黑树✅有序唯一双向迭代器\(O(logN)\)支持operatorunordered_set开链哈希桶❌无序唯一单向迭代器平均\(O(1)\)最坏\(O(N)\)可哈希 支持operatormap红黑树✅有序唯一双向迭代器\(O(logN)\)支持operatorunordered_map开链哈希桶❌无序唯一单向迭代器平均\(O(1)\)最坏\(O(N)\)可哈希 支持operatormultiset红黑树✅有序允许重复双向迭代器\(O(logN)\)支持operatorunordered_multiset开链哈希桶❌无序允许重复单向迭代器平均\(O(1)\)最坏\(O(N)\)可哈希 支持operator二、模板参数完整拆解unordered_set 模板原型template class Key, class Hash hashKey, //哈希仿函数 class Pred equal_toKey, //相等比较仿函数 class Alloc allocatorKey //内存分配器 class unordered_set;Key存储的关键字类型。Hash hashKey哈希仿函数接收 key返回size_t无符号整数哈希值内置类型int、std::string标准库已经提供hash特化版本直接可用。自定义类型作为 key必须自己提供哈希仿函数。Pred equal_toKey相等比较仿函数用来判断两个 key 是否完全等价底层调用operator。自定义类型做 key需要重载运算符。Alloc空间配置器管理内存申请释放业务开发几乎不需要手动传入。unordered_map 模板原型templateclass Key, class T, class Hash hashKey, class Pred equal_toKey, class Alloc allocatorpairconst Key, T class unordered_map;存储元素类型pairconst Key, Tkey是const不允许修改避免哈希定位错乱value 可以正常读写。和map一样支持operator[]unordered_multimap 依然不支持 operator []。对外常用接口和 map/set 保持一致//插入返回pair迭代器,boolbool标记插入是否成功key是否重复 pairiterator,bool insert(const value_type val); //按key删除 size_type erase(const key_type k); //查找key找不到返回end() iterator find(const key_type k); //unordered_map独有 mapped_type operator[](const key_type k);三、底层开链哈希桶拉链法unordered 系列底层是数组 单向链表的组合数组每一个下标位置称为一个bucket哈希桶。插入流程① 使用哈希仿函数对 key 计算得到size_t哈希值 ② 哈希值对桶数组的总容量取模得到桶数组下标 ③ 将结点挂到该下标对应的单向链表尾部。查找流程① 计算 key 哈希值取模定位对应桶 ② 遍历这个桶内单向链表使用逐个对比 key ③ 找到匹配 key 返回迭代器遍历完链表没找到返回end()。删除流程① 哈希定位桶 ② 遍历桶内链表找到目标结点 ③ 链表删除该结点释放内存。哈希冲突哈希冲突不同的 key经过哈希计算取模之后落到同一个哈希桶。哈希值不一样取模之后下标相同同样会落到同一个桶。 ⚠重点哈希值相等不代表 key 相等哈希值不等key 一定不相等。所以定位桶依靠哈希桶内部区分真正相等 key必须依靠相等判断两套逻辑缺一不可。开链法解决冲突冲突的结点全部挂载同一个桶下的单向链表不会覆盖原有数据。 缺点如果大量元素集中落在少数桶链表越来越长查找就要遍历长链表性能退化。负载因子 load_factor\(load\_factor \frac{容器有效元素数量size}{哈希桶总数量bucket\_count}\)max_load_factor负载因子阈值STL 默认值为1.0。当load_factor max_load_factor触发rehash 重哈希。rehash 重哈希机制【面试高频】rehash 会开辟一块更大的哈希桶数组通常扩大到原来倍数遍历旧哈希表全部结点每一个 key 重新计算哈希重新取模挂载到新桶数组对应链表全部迁移完成后释放旧桶数组内存。重大后果rehash 之后全部旧迭代器全部失效因为结点迁移到新内存旧迭代器指向已经释放的旧数组解引用属于未定义行为。优化手段如果预先知道要插入多少数据可以调用reserve(n)提前预分配足够桶减少运行过程中频繁 rehash 带来性能抖动。unordered_setint us; us.reserve(100000); //预留足够桶降低rehash触发次数哈希相关专属接口业务写代码很少用底层调试、面试会考bucket_count() // 当前桶总数量 load_factor() // 当前负载因子 max_load_factor() // 获取/设置最大负载因子阈值 rehash(n) // 手动指定至少n个桶强制重哈希 reserve(n) // 根据元素数量n自动计算合适桶数预分配四、迭代器核心特性unordered_xxx 是单向迭代器仅支持前置 / 后置it不支持--it反向迭代没有rbegin()、rend()。对比 map/set 双向迭代器--都支持底层红黑树结点保存 parent 父指针可以向上回溯。 unordered 底层是单向链表结点没有向前指针无法后退。迭代器失效规则rehash 重哈希全部迭代器直接失效。insert 插入只要不触发 rehash现有迭代器保持有效一旦 rehash全部失效。erase 删除只有被删除结点迭代器失效其余迭代器仍然有效。遍历无序unordered 容器遍历输出顺序和插入顺序无关顺序由哈希值、桶下标、链表顺序共同决定。即使同样输入rehash 之后遍历顺序会改变。 ⚠禁止业务代码依赖 unordered 的遍历顺序测试偶然有序只是巧合。五、key 的约束条件面试高频map /set红黑树只需要 key 支持operator小于比较内部依靠小于完成排序搜索。unordered_set /unordered_map哈希两个条件必须同时满足key 可以被哈希hashKey能够得到 size_t 哈希值key 支持相等比较重载operator。面试题自定义结构体作为 unordered_set 的 key 需要做什么编写自定义哈希仿函数把结构体成员混合计算返回size_t哈希值对结构体重载operator判断两个对象 key 语义等价。对比自定义结构体作为 set/map 的 key只需要重载 operator。示例伪代码struct Student { int id; string name; bool operator(const Student other) const { return id other.id; } }; //自定义哈希仿函数 struct HashStudent { size_t operator()(const Student s) const { return hashint()(s.id); } }; //使用传入自定义哈希 unordered_setStudent, HashStudent st;六、unordered_multiset /unordered_multimap特性允许 key 重复底层依然是开链哈希桶无序单向迭代器。insert允许插入重复 key返回的 pair 中 bool 无实际意义。find(key)返回桶链表中第一个匹配 key 的迭代器。erase(key)按值删除会删除容器中全部等于 key 的元素只想删除其中一个必须传入迭代器erase(it)。unordered_multimap不支持 operator []和 multimap 保持一致重复 key 无法确定取哪一个 value。七、map/set 和 unordered_map/unordered_set 选型策略✅优先选择 unordered哈希业务只做单点增、删、查不需要 key 有序输出数据量大追求平均性能缺点存在 rehash 性能抖动最坏退化 O (N)内存开销更大单向迭代器无序。✅优先选择 map /set红黑树需要 key 有序输出需要区间操作使用lower_bound、upper_bound做范围查询哈希表无法高效区间查找业务对最坏时间复杂度有硬性要求需要稳定\(O(logN)\)不能接受退化到 O (N)需要反向遍历依赖双向迭代器--it。补充小数据规模场景\(O(logN)\)红黑树与哈希\(O(1)\)差距很小哈希还要计算哈希值、处理 rehash甚至性能不如红黑树。八、完整性能测试参考代码对比 set 与 unordered_set 插入、查找、删除耗时直观体现性能差异。#include unordered_set #include set #include iostream #include vector #include ctime using namespace std; int main() { const size_t N 1000000; vectorint v; v.reserve(N); srand((unsigned int)time(nullptr)); for(size_t i 0; i N; i) { v.push_back(rand() i); } setint s; unordered_setint us; //插入计时 size_t begin1 clock(); for(auto e : v) s.insert(e); size_t end1 clock(); cout set insert time: 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 time: end2 - begin2 endl; //查找计时 size_t begin3 clock(); for(auto e : v) s.find(e); size_t end3 clock(); cout set find time: end3 - begin3 endl; size_t begin4 clock(); for(auto e : v) us.find(e); size_t end4 clock(); cout unordered_set find time: end4 - begin4 endl; return 0; }现象大数据量哈希容器平均速度更快如果哈希函数设计差大量冲突性能会大幅下跌。九、大厂面试高频问题总结Q1map 与 unordered_map 底层时间复杂度map 底层红黑树平衡二叉搜索树增删查稳定\(O(logN)\)key 有序双向迭代器支持lower_bound区间查找。 unordered_map 底层开链哈希桶平均\(O(1)\)哈希冲突严重最坏退化\(O(N)\)遍历无序单向迭代器rehash 会全部迭代器失效。Q2unordered 为什么既需要 hash 函数又需要 运算符hash 函数只用来计算哈希值定位哈希桶不同 key 可以算出相同哈希值发生哈希冲突同一个桶链表内必须用来真正判断两个 key 是否等价哈希值相等不等于 key 相等。Q3rehash 重哈希做了什么会带来什么问题负载因子超过阈值 max_load_factor开辟更大哈希桶数组把旧容器中全部结点重新计算哈希迁移挂载到新桶数组释放旧内存。rehash 发生之后所有旧迭代器全部失效。可以使用reserve()提前预分配减少 rehash。Q4unordered 的迭代器为什么不支持 -- 反向迭代底层桶内是单向链表结点只有后继指针没有前驱指针只能向后遍历属于单向迭代器红黑树结点保存 parent 父指针可以向上回溯支持双向迭代。Q5multimap 和 unordered_multimap 的区别multimap 底层红黑树key 有序双向迭代器允许重复 key unordered_multimap 底层开链哈希桶无序单向迭代器允许重复 key二者都不支持operator[]按 key 调用 erase会删除全部匹配 key。Q6什么时候用 map什么时候用 unordered_map需要 key 有序、区间查找、要求最坏时间复杂度稳定\(O(logN)\) → map 大数据量只做单点增删查不需要有序追求平均性能 → unordered_map 小数据量两者性能差距不大。Q7自定义结构体作为 unordered_map 的 key需要实现什么自定义哈希仿函数返回size_t哈希值重载operator实现 key 相等语义如果作为 map 的 key仅需要重载operator。十、开发易错坑清单❌不要依赖 unordered 容器的遍历顺序环境、rehash 都会改变输出顺序❌rehash 之后继续使用旧迭代器解引用直接未定义行为❌自定义结构体当 key 忘记提供哈希仿函数直接编译报错❌unordered_multimap使用operator[]编译报错不支持✅已知插入数据规模优先调用reserve(n)减少 rehash 带来性能抖动。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Jev模型TypeSafe AI开发实战:从密钥申请到结构化输出 2026/9/30 10:15:18

Jev模型TypeSafe AI开发实战:从密钥申请到结构化输出

1. 从热搜词里读懂 Jev 到底是个什么东西Jev 模型最近在技术圈刷屏,但很多人第一次看到这个名字的时候是懵的——它到底是一个聊天模型、一个开发框架,还是一套 SDK?我花了两天时间把官网文档、GitHub 仓库和社区讨论翻了个遍,又实…

阅读更多 →
机器视觉与机械臂手眼标定闭环抓取系统设计 2026/9/30 10:15:18

机器视觉与机械臂手眼标定闭环抓取系统设计

简介:本资源是一份面向机器人工程、智能制造与人工智能方向高校师生及研发工程师的系统级设计参考文档,聚焦机器视觉驱动的机械臂自适应抓取问题,解决传统示教型机器人在非结构化环境中定位精度低、泛化能力弱的痛点。文档完整呈现了目标识别…

阅读更多 →
本地网关整合14个免费模型通道:自动路由与容错实践 2026/9/30 10:15:18

本地网关整合14个免费模型通道:自动路由与容错实践

1. 为什么要把十几个免费模型通道塞进一个入口我最早用 WorkBuddy 的时候,配置里塞了七八个免费模型的接入点,每次切换任务都得手动改一遍配置,写代码用一个、写文档换一个、做翻译再换一个,一天下来光切模型就浪费不少时间。后来…

阅读更多 →
Laya轻量级决策路由框架:ModernBERT与温度拟合端侧部署实战 2026/9/30 10:15:11

Laya轻量级决策路由框架:ModernBERT与温度拟合端侧部署实战

1. 从17K Star说起:Laya到底是个什么东西 第一次在技术社区刷到Laya这个项目的时候,我正被一个决策类项目的推理延迟折磨得够呛。当时的需求很明确:要在端侧设备上跑一个能处理多轮对话、还能做工具调用的决策模型,但试了好几个方…

阅读更多 →
城市道路晒粮检测数据集:1065张真实图+VOC/YOLO双格式 2026/9/30 10:15:11

城市道路晒粮检测数据集:1065张真实图+VOC/YOLO双格式

简介:本资源是面向智慧交通与计算机视觉初学者的轻量级目标检测数据集,聚焦城市道路场景下“打场晒粮”这一典型违章行为的识别任务,适用于YOLO、Faster R-CNN等主流检测模型的训练与验证。数据集共1065张高质量JPG图像,全部标注为…

阅读更多 →
ZooKeeper集群搭建完整指南:从配置到故障排查 2026/9/30 10:15:04

ZooKeeper集群搭建完整指南:从配置到故障排查

先说个我自己当年的经历:第一次搭ZooKeeper集群时,照着教程准备三台机器、三份配置,依次启动之后执行zkServer.sh status,三台全是follower。我一度以为配置错了,反复删 data 目录重来,最后才明白&#xff…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉