哈喽,编程搭子们!😜 又到了沉浸式敲代码的快乐时间~把生活调成「代码模式」,带着满满的热爱钻进编程的奇妙世界------今天也要敲出超酷的代码,冲鸭!🚀

✨ 我的博客主页:喜欢吃燃面
📚 我的专栏(持续更新ing):
《C语言》 |
《数据结构》 |
《C++》 |
《Linux系统编程》
💖 超感谢你点开这篇博客!真心希望这些内容能帮到正在打怪升级的你~如果有任何想法、疑问,或者想交流学习心得,都欢迎留言/私信,咱们一起在编程路上互相陪伴、共同进步呀!
摘要 :在 C++ 标准模板库(STL)中,
std::set与std::unordered_set、std::map与std::unordered_map是两组功能相似但底层截然不同的关联容器。前者基于红黑树 实现,后者则基于哈希表(散列表) 。本文将从容器接口的差异出发,逐步深入到哈希函数、冲突解决、链地址法、扩容重哈希(Rehash)以及迭代器的封装实现,带你完整剖析unordered系列容器的底层世界。
一、从有序到无序:关联容器的分野
在 C++ 中,当我们需要存储不重复 的关键字集合时,首先想到的往往是 std::set。它基于红黑树实现,提供了 O(log N) 的增删查改效率,并且迭代器遍历是有序的 (中序遍历)。然而,在许多场景下,我们并不关心元素是否有序,而是追求极致的平均常数时间复杂度 。这时,std::unordered_set 和 std::unordered_map 便应运而生。
1.1 类模板概览
std::unordered_set 的声明如下(简化版):
cpp
template <class Key,
class Hash = hash<Key>,
class Pred = equal_to<Key>,
class Alloc = allocator<Key>>
class unordered_set;
与之对比,std::set 的模板参数则完全不同:
cpp
template <class T,
class Compare = less<T>,
class Alloc = allocator<T>>
class set;
从模板参数的差异中,我们可以窥见两者底层逻辑的截然不同:
unordered_set要求传入一个Hash仿函数,用于将Key转换为整型(哈希值);还需要Pred(默认为equal_to),用于判断两个Key是否相等。set则要求传入一个Compare仿函数(默认为less),用于判断Key的大小关系。
关键洞察 :
set依赖比较 (<),而unordered_set依赖哈希 (转为整型)和相等判断 (==)。这是理解两者本质差异的第一把钥匙。
1.2 核心特性梳理
unordered_set 的几个核心特性值得我们牢记:
- 底层结构 :采用哈希桶(Hash Bucket)实现,即经典的链地址法哈希表。
- 时间复杂度 :增删查改的平均时间复杂度为 O ( 1 ) O(1) O(1) ;最坏情况下(所有元素哈希冲突)退化至 O ( N ) O(N) O(N)。
- 迭代器特性 :迭代器为单向迭代器(Forward Iterator) ,且遍历结果是无序的。
- 内存管理:底层存储数据的内存从空间配置器(Allocator)中申请,一般情况下我们无需手动指定后三个模板参数。
1.3 unordered_set 与 set 的使用差异
虽然两者在接口层面都支持增删查改,且使用方式高度相似,但它们的差异体现在三个维度:
| 维度 | std::set |
std::unordered_set |
|---|---|---|
| 对 Key 的要求 | 支持 < 比较 |
支持转为整型(哈希)+ == 比较 |
| 迭代器 | 双向迭代器,中序有序 | 单向迭代器,无序+去重 |
| 性能 | 增删查改 O ( log N ) O(\log N) O(logN) | 增删查改平均 O ( 1 ) O(1) O(1) |
实践建议 :在大多数不需要有序遍历的场景下,
unordered_set的增删查改性能显著优于set。只有在需要按序遍历,或者需要自定义的复杂顺序时,才优先考虑set。
同理,std::unordered_map 与 std::map 也遵循完全相同的底层逻辑差异。unordered_map 存储的是 pair<const Key, T>,其底层同样是哈希桶。
二、哈希表的基石:从直接定址到除留余数
要理解 unordered_set 的底层,必须先理解**哈希表(Hash Table)**的基本原理。哈希表的核心思想是:通过某种函数关系,将关键字(Key)直接映射到存储地址,从而实现 O ( 1 ) O(1) O(1) 的访问效率。
2.1 直接定址法:理想但局限
当关键字的范围比较集中时,直接定址法是最简单高效的方法。
定义 :直接定址法本质上是利用关键字计算出一个绝对位置 或相对位置。
示例 1 :如果一组关键字都在 [0, 99] 之间,那么我们可以直接开一个大小为 100 的数组,关键字的值就是存储位置的下标。
示例 2 :如果关键字都是 [a, z] 的小写字母,那么开一个大小为 26 的数组,通过 ascii码 - 'a' 即可得到存储位置的下标。
#mermaid-svg-68w83HzTofPbAASd{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-68w83HzTofPbAASd .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-68w83HzTofPbAASd .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-68w83HzTofPbAASd .error-icon{fill:#552222;}#mermaid-svg-68w83HzTofPbAASd .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-68w83HzTofPbAASd .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-68w83HzTofPbAASd .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-68w83HzTofPbAASd .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-68w83HzTofPbAASd .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-68w83HzTofPbAASd .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-68w83HzTofPbAASd .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-68w83HzTofPbAASd .marker{fill:#333333;stroke:#333333;}#mermaid-svg-68w83HzTofPbAASd .marker.cross{stroke:#333333;}#mermaid-svg-68w83HzTofPbAASd svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-68w83HzTofPbAASd p{margin:0;}#mermaid-svg-68w83HzTofPbAASd .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-68w83HzTofPbAASd .cluster-label text{fill:#333;}#mermaid-svg-68w83HzTofPbAASd .cluster-label span{color:#333;}#mermaid-svg-68w83HzTofPbAASd .cluster-label span p{background-color:transparent;}#mermaid-svg-68w83HzTofPbAASd .label text,#mermaid-svg-68w83HzTofPbAASd span{fill:#333;color:#333;}#mermaid-svg-68w83HzTofPbAASd .node rect,#mermaid-svg-68w83HzTofPbAASd .node circle,#mermaid-svg-68w83HzTofPbAASd .node ellipse,#mermaid-svg-68w83HzTofPbAASd .node polygon,#mermaid-svg-68w83HzTofPbAASd .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-68w83HzTofPbAASd .rough-node .label text,#mermaid-svg-68w83HzTofPbAASd .node .label text,#mermaid-svg-68w83HzTofPbAASd .image-shape .label,#mermaid-svg-68w83HzTofPbAASd .icon-shape .label{text-anchor:middle;}#mermaid-svg-68w83HzTofPbAASd .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-68w83HzTofPbAASd .rough-node .label,#mermaid-svg-68w83HzTofPbAASd .node .label,#mermaid-svg-68w83HzTofPbAASd .image-shape .label,#mermaid-svg-68w83HzTofPbAASd .icon-shape .label{text-align:center;}#mermaid-svg-68w83HzTofPbAASd .node.clickable{cursor:pointer;}#mermaid-svg-68w83HzTofPbAASd .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-68w83HzTofPbAASd .arrowheadPath{fill:#333333;}#mermaid-svg-68w83HzTofPbAASd .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-68w83HzTofPbAASd .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-68w83HzTofPbAASd .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-68w83HzTofPbAASd .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-68w83HzTofPbAASd .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-68w83HzTofPbAASd .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-68w83HzTofPbAASd .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-68w83HzTofPbAASd .cluster text{fill:#333;}#mermaid-svg-68w83HzTofPbAASd .cluster span{color:#333;}#mermaid-svg-68w83HzTofPbAASd div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-68w83HzTofPbAASd .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-68w83HzTofPbAASd rect.text{fill:none;stroke-width:0;}#mermaid-svg-68w83HzTofPbAASd .icon-shape,#mermaid-svg-68w83HzTofPbAASd .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-68w83HzTofPbAASd .icon-shape p,#mermaid-svg-68w83HzTofPbAASd .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-68w83HzTofPbAASd .icon-shape rect,#mermaid-svg-68w83HzTofPbAASd .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-68w83HzTofPbAASd .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-68w83HzTofPbAASd .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-68w83HzTofPbAASd :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 直接作为下标
ascii('c') - ascii('a') = 2
Key: 42
数组索引 42
Key: 'c'
数组索引 2
然而,直接定址法的缺点也极其明显:它只适合范围集中的整型 。如果我们有 100 个整型值,但它们的范围分布在 0 到 100w 之间,直接开一个长度为 100w 的数组会造成巨大的内存浪费,甚至内存不足。
2.2 除留余数法:散列的艺术
为了应对关键字范围分散的情况,我们需要一种能将任意范围的整数"压缩"到固定区间 [0, M) 的方法。这就是除法散列法 ,又称除留余数法。
公式 :假设哈希表的大小为 M M M,则哈希函数定义为:
h ( k e y ) = k e y % M h(key) = key \ \% \ M h(key)=key % M
通过取余运算,无论 key 原本的范围有多大,最终都会被映射到 [0, M-1] 的区间内。
但这里隐藏着一个关键问题:不同的 key 可能计算出相同的哈希值 。例如,当 M = 11 M=11 M=11 时,19 % 11 = 8,30 % 11 = 8。两个不同的关键字被映射到了同一个位置,这种现象被称为哈希冲突(Hash Collision)。
#mermaid-svg-rWTG53a6pmC9aZMM{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-rWTG53a6pmC9aZMM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-rWTG53a6pmC9aZMM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-rWTG53a6pmC9aZMM .error-icon{fill:#552222;}#mermaid-svg-rWTG53a6pmC9aZMM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-rWTG53a6pmC9aZMM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-rWTG53a6pmC9aZMM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-rWTG53a6pmC9aZMM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-rWTG53a6pmC9aZMM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-rWTG53a6pmC9aZMM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-rWTG53a6pmC9aZMM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-rWTG53a6pmC9aZMM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-rWTG53a6pmC9aZMM .marker.cross{stroke:#333333;}#mermaid-svg-rWTG53a6pmC9aZMM svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-rWTG53a6pmC9aZMM p{margin:0;}#mermaid-svg-rWTG53a6pmC9aZMM .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-rWTG53a6pmC9aZMM .cluster-label text{fill:#333;}#mermaid-svg-rWTG53a6pmC9aZMM .cluster-label span{color:#333;}#mermaid-svg-rWTG53a6pmC9aZMM .cluster-label span p{background-color:transparent;}#mermaid-svg-rWTG53a6pmC9aZMM .label text,#mermaid-svg-rWTG53a6pmC9aZMM span{fill:#333;color:#333;}#mermaid-svg-rWTG53a6pmC9aZMM .node rect,#mermaid-svg-rWTG53a6pmC9aZMM .node circle,#mermaid-svg-rWTG53a6pmC9aZMM .node ellipse,#mermaid-svg-rWTG53a6pmC9aZMM .node polygon,#mermaid-svg-rWTG53a6pmC9aZMM .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-rWTG53a6pmC9aZMM .rough-node .label text,#mermaid-svg-rWTG53a6pmC9aZMM .node .label text,#mermaid-svg-rWTG53a6pmC9aZMM .image-shape .label,#mermaid-svg-rWTG53a6pmC9aZMM .icon-shape .label{text-anchor:middle;}#mermaid-svg-rWTG53a6pmC9aZMM .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-rWTG53a6pmC9aZMM .rough-node .label,#mermaid-svg-rWTG53a6pmC9aZMM .node .label,#mermaid-svg-rWTG53a6pmC9aZMM .image-shape .label,#mermaid-svg-rWTG53a6pmC9aZMM .icon-shape .label{text-align:center;}#mermaid-svg-rWTG53a6pmC9aZMM .node.clickable{cursor:pointer;}#mermaid-svg-rWTG53a6pmC9aZMM .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-rWTG53a6pmC9aZMM .arrowheadPath{fill:#333333;}#mermaid-svg-rWTG53a6pmC9aZMM .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-rWTG53a6pmC9aZMM .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-rWTG53a6pmC9aZMM .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rWTG53a6pmC9aZMM .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-rWTG53a6pmC9aZMM .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rWTG53a6pmC9aZMM .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-rWTG53a6pmC9aZMM .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-rWTG53a6pmC9aZMM .cluster text{fill:#333;}#mermaid-svg-rWTG53a6pmC9aZMM .cluster span{color:#333;}#mermaid-svg-rWTG53a6pmC9aZMM div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-rWTG53a6pmC9aZMM .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-rWTG53a6pmC9aZMM rect.text{fill:none;stroke-width:0;}#mermaid-svg-rWTG53a6pmC9aZMM .icon-shape,#mermaid-svg-rWTG53a6pmC9aZMM .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rWTG53a6pmC9aZMM .icon-shape p,#mermaid-svg-rWTG53a6pmC9aZMM .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-rWTG53a6pmC9aZMM .icon-shape rect,#mermaid-svg-rWTG53a6pmC9aZMM .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rWTG53a6pmC9aZMM .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-rWTG53a6pmC9aZMM .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-rWTG53a6pmC9aZMM :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 19 % 11
30 % 11
5 % 11
36 % 11
Key: 19
哈希地址: 8
Key: 30
Key: 5
哈希地址: 5
Key: 36
哈希地址: 3
2.3 哈希函数的设计:均匀分布是终极目标
一个好的哈希函数应该让 N N N 个关键字等概率、均匀地 散列到哈希表的 M M M 个空间中。虽然实际中很难做到绝对均匀,但我们应该尽量往这个方向设计。
在使用除留余数法时, M M M 的取值至关重要:
- 避免 M M M 为 2 X 2^X 2X :如果 M = 2 X M = 2^X M=2X,那么
key % M的本质相当于只保留key的后 X X X 位二进制 。例如,63的二进制是00111111,31的二进制是00011111,若 M = 16 M=16 M=16(即 2 4 2^4 24),两者后 4 位都是1111,哈希值均为15,冲突概率极高。 - 避免 M M M 为 10 X 10^X 10X :同理,这相当于只保留十进制的后 X X X 位,如
112和12312对100取模结果都是12。 - 建议 M M M 取质数 :理论研究表明, M M M 取一个不太接近 2 的整数次幂的质数(素数),可以显著减少冲突。
Java 的启示 :Java 的
HashMap在采用除法散列时,哈希表大小 M M M 取的是 2 的整数次幂。这并非为了取模,而是为了用**位运算(&)**替代取模运算,提升效率。为了避免冲突,Java 会对key的哈希值进行扰动处理(如高位与低位异或),让key的所有位都参与运算,从而使哈希值分布更均匀。这告诉我们:理论是指导,实践需灵活。
三、冲突的解决:开放定址与链地址
哈希冲突是不可避免的。当冲突发生时,我们必须有相应的策略来安置那些"无家可归"的元素。常见的冲突解决方法有两类:开放定址法(Open Addressing)和链地址法(Separate Chaining)。
3.1 开放定址法:线性探测
开放定址法的核心思想是:如果当前位置被占用了,就按照某种规则探测下一个空位置。
线性探测是最直观的开放定址策略。其公式为:
h c ( k e y , i ) = ( h a s h 0 + i ) % M , i = { 1 , 2 , 3 , . . . , M − 1 } h_c(key, i) = (hash_0 + i) \ \% \ M, \quad i = \{1, 2, 3, ..., M-1\} hc(key,i)=(hash0+i) % M,i={1,2,3,...,M−1}
其中 h a s h 0 = k e y % M hash_0 = key \ \% \ M hash0=key % M 是初始哈希位置。如果该位置冲突,则依次探测下一个位置( i = 1 i=1 i=1),再下一个( i = 2 i=2 i=2),直到找到空位。
示例 :将序列 {19, 30, 5, 36, 13, 20, 21, 12} 映射到 M = 11 M=11 M=11 的表中。
- h ( 19 ) = 19 % 11 = 8 h(19) = 19 \% 11 = 8 h(19)=19%11=8
- h ( 30 ) = 30 % 11 = 8 h(30) = 30 \% 11 = 8 h(30)=30%11=8 → 冲突!探测 i = 1 i=1 i=1: ( 8 + 1 ) % 11 = 9 (8+1)\%11 = 9 (8+1)%11=9,存入 9 号位。
- h ( 5 ) = 5 % 11 = 5 h(5) = 5 \% 11 = 5 h(5)=5%11=5
- h ( 36 ) = 36 % 11 = 3 h(36) = 36 \% 11 = 3 h(36)=36%11=3
- h ( 13 ) = 13 % 11 = 2 h(13) = 13 \% 11 = 2 h(13)=13%11=2
- h ( 20 ) = 20 % 11 = 9 h(20) = 20 \% 11 = 9 h(20)=20%11=9 → 冲突!探测 i = 1 i=1 i=1: ( 9 + 1 ) % 11 = 10 (9+1)\%11 = 10 (9+1)%11=10,存入 10 号位。
- h ( 21 ) = 21 % 11 = 10 h(21) = 21 \% 11 = 10 h(21)=21%11=10 → 冲突!探测 i = 1 i=1 i=1: ( 10 + 1 ) % 11 = 0 (10+1)\%11 = 0 (10+1)%11=0,存入 0 号位。
- h ( 12 ) = 12 % 11 = 1 h(12) = 12 \% 11 = 1 h(12)=12%11=1
最终哈希表状态如下:
#mermaid-svg-HyudB2WM7OUMfq2K{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-HyudB2WM7OUMfq2K .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-HyudB2WM7OUMfq2K .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-HyudB2WM7OUMfq2K .error-icon{fill:#552222;}#mermaid-svg-HyudB2WM7OUMfq2K .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-HyudB2WM7OUMfq2K .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-HyudB2WM7OUMfq2K .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-HyudB2WM7OUMfq2K .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-HyudB2WM7OUMfq2K .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-HyudB2WM7OUMfq2K .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-HyudB2WM7OUMfq2K .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-HyudB2WM7OUMfq2K .marker{fill:#333333;stroke:#333333;}#mermaid-svg-HyudB2WM7OUMfq2K .marker.cross{stroke:#333333;}#mermaid-svg-HyudB2WM7OUMfq2K svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-HyudB2WM7OUMfq2K p{margin:0;}#mermaid-svg-HyudB2WM7OUMfq2K .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-HyudB2WM7OUMfq2K .cluster-label text{fill:#333;}#mermaid-svg-HyudB2WM7OUMfq2K .cluster-label span{color:#333;}#mermaid-svg-HyudB2WM7OUMfq2K .cluster-label span p{background-color:transparent;}#mermaid-svg-HyudB2WM7OUMfq2K .label text,#mermaid-svg-HyudB2WM7OUMfq2K span{fill:#333;color:#333;}#mermaid-svg-HyudB2WM7OUMfq2K .node rect,#mermaid-svg-HyudB2WM7OUMfq2K .node circle,#mermaid-svg-HyudB2WM7OUMfq2K .node ellipse,#mermaid-svg-HyudB2WM7OUMfq2K .node polygon,#mermaid-svg-HyudB2WM7OUMfq2K .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-HyudB2WM7OUMfq2K .rough-node .label text,#mermaid-svg-HyudB2WM7OUMfq2K .node .label text,#mermaid-svg-HyudB2WM7OUMfq2K .image-shape .label,#mermaid-svg-HyudB2WM7OUMfq2K .icon-shape .label{text-anchor:middle;}#mermaid-svg-HyudB2WM7OUMfq2K .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-HyudB2WM7OUMfq2K .rough-node .label,#mermaid-svg-HyudB2WM7OUMfq2K .node .label,#mermaid-svg-HyudB2WM7OUMfq2K .image-shape .label,#mermaid-svg-HyudB2WM7OUMfq2K .icon-shape .label{text-align:center;}#mermaid-svg-HyudB2WM7OUMfq2K .node.clickable{cursor:pointer;}#mermaid-svg-HyudB2WM7OUMfq2K .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-HyudB2WM7OUMfq2K .arrowheadPath{fill:#333333;}#mermaid-svg-HyudB2WM7OUMfq2K .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-HyudB2WM7OUMfq2K .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-HyudB2WM7OUMfq2K .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HyudB2WM7OUMfq2K .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-HyudB2WM7OUMfq2K .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HyudB2WM7OUMfq2K .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-HyudB2WM7OUMfq2K .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-HyudB2WM7OUMfq2K .cluster text{fill:#333;}#mermaid-svg-HyudB2WM7OUMfq2K .cluster span{color:#333;}#mermaid-svg-HyudB2WM7OUMfq2K div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-HyudB2WM7OUMfq2K .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-HyudB2WM7OUMfq2K rect.text{fill:none;stroke-width:0;}#mermaid-svg-HyudB2WM7OUMfq2K .icon-shape,#mermaid-svg-HyudB2WM7OUMfq2K .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HyudB2WM7OUMfq2K .icon-shape p,#mermaid-svg-HyudB2WM7OUMfq2K .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-HyudB2WM7OUMfq2K .icon-shape rect,#mermaid-svg-HyudB2WM7OUMfq2K .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HyudB2WM7OUMfq2K .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-HyudB2WM7OUMfq2K .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-HyudB2WM7OUMfq2K :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 线性探测结果 M=11
0: 21
1: 12
2: 13
3: 36
4: 空
5: 5
6: 空
7: 空
8: 19
9: 30
10: 20
线性探测的缺陷也很明显:容易产生"堆积"现象。连续的冲突会导致一片连续的区域被占满,后续插入需要探测很多次,效率下降。此外,删除操作也变得复杂------不能简单地将位置置空,否则会导致查找时误判(认为后面的元素不存在)。通常需要标记为"已删除"状态。
3.2 双重探测:更聪明的步长
为了缓解线性探测的堆积问题,**双重探测(Double Hashing)**采用两个哈希函数。当冲突发生时,探测的步长不再固定为 1,而是由第二个哈希函数决定。
公式 :
h c ( k e y , i ) = ( h a s h 0 + i × h 2 ( k e y ) ) % M , i = { 1 , 2 , 3 , . . . , M } h_c(key, i) = (hash_0 + i \times h_2(key)) \ \% \ M, \quad i = \{1, 2, 3, ..., M\} hc(key,i)=(hash0+i×h2(key)) % M,i={1,2,3,...,M}
其中 h a s h 0 = k e y % M hash_0 = key \ \% \ M hash0=key % M。关键在于,h 2 ( k e y ) h_2(key) h2(key) 必须与 M M M 互质 。这是因为根据数论,如果偏移量与表长 M M M 的最大公约数 p = gcd ( M , h 2 ( k e y ) ) > 1 p = \gcd(M, h_2(key)) > 1 p=gcd(M,h2(key))>1,那么所能寻址的位置个数将减少为 M / p M/p M/p,无法覆盖整个表。
示例 :设 M = 11 M=11 M=11, h 2 ( k e y ) = k e y % 10 + 1 h_2(key) = key \% 10 + 1 h2(key)=key%10+1。插入序列 {19, 30, 52, 74}。
- h ( 19 ) = 19 % 11 = 8 h(19) = 19 \% 11 = 8 h(19)=19%11=8
- h ( 30 ) = 30 % 11 = 8 h(30) = 30 \% 11 = 8 h(30)=30%11=8 → 冲突! h 2 ( 30 ) = 30 % 10 + 1 = 1 h_2(30) = 30\%10+1 = 1 h2(30)=30%10+1=1。探测: ( 8 + 1 × 1 ) % 11 = 9 (8 + 1\times1)\%11 = 9 (8+1×1)%11=9。
- h ( 52 ) = 52 % 11 = 8 h(52) = 52 \% 11 = 8 h(52)=52%11=8 → 冲突! h 2 ( 52 ) = 52 % 10 + 1 = 3 h_2(52) = 52\%10+1 = 3 h2(52)=52%10+1=3。探测: ( 8 + 1 × 3 ) % 11 = 0 (8 + 1\times3)\%11 = 0 (8+1×3)%11=0。
- h ( 74 ) = 74 % 11 = 8 h(74) = 74 \% 11 = 8 h(74)=74%11=8 → 冲突! h 2 ( 74 ) = 74 % 10 + 1 = 5 h_2(74) = 74\%10+1 = 5 h2(74)=74%10+1=5。探测: ( 8 + 1 × 5 ) % 11 = 2 (8 + 1\times5)\%11 = 2 (8+1×5)%11=2。
#mermaid-svg-Iv5EauAoYJdTWj0P{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Iv5EauAoYJdTWj0P .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Iv5EauAoYJdTWj0P .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Iv5EauAoYJdTWj0P .error-icon{fill:#552222;}#mermaid-svg-Iv5EauAoYJdTWj0P .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Iv5EauAoYJdTWj0P .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Iv5EauAoYJdTWj0P .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Iv5EauAoYJdTWj0P .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Iv5EauAoYJdTWj0P .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Iv5EauAoYJdTWj0P .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Iv5EauAoYJdTWj0P .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Iv5EauAoYJdTWj0P .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Iv5EauAoYJdTWj0P .marker.cross{stroke:#333333;}#mermaid-svg-Iv5EauAoYJdTWj0P svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Iv5EauAoYJdTWj0P p{margin:0;}#mermaid-svg-Iv5EauAoYJdTWj0P .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Iv5EauAoYJdTWj0P .cluster-label text{fill:#333;}#mermaid-svg-Iv5EauAoYJdTWj0P .cluster-label span{color:#333;}#mermaid-svg-Iv5EauAoYJdTWj0P .cluster-label span p{background-color:transparent;}#mermaid-svg-Iv5EauAoYJdTWj0P .label text,#mermaid-svg-Iv5EauAoYJdTWj0P span{fill:#333;color:#333;}#mermaid-svg-Iv5EauAoYJdTWj0P .node rect,#mermaid-svg-Iv5EauAoYJdTWj0P .node circle,#mermaid-svg-Iv5EauAoYJdTWj0P .node ellipse,#mermaid-svg-Iv5EauAoYJdTWj0P .node polygon,#mermaid-svg-Iv5EauAoYJdTWj0P .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Iv5EauAoYJdTWj0P .rough-node .label text,#mermaid-svg-Iv5EauAoYJdTWj0P .node .label text,#mermaid-svg-Iv5EauAoYJdTWj0P .image-shape .label,#mermaid-svg-Iv5EauAoYJdTWj0P .icon-shape .label{text-anchor:middle;}#mermaid-svg-Iv5EauAoYJdTWj0P .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Iv5EauAoYJdTWj0P .rough-node .label,#mermaid-svg-Iv5EauAoYJdTWj0P .node .label,#mermaid-svg-Iv5EauAoYJdTWj0P .image-shape .label,#mermaid-svg-Iv5EauAoYJdTWj0P .icon-shape .label{text-align:center;}#mermaid-svg-Iv5EauAoYJdTWj0P .node.clickable{cursor:pointer;}#mermaid-svg-Iv5EauAoYJdTWj0P .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Iv5EauAoYJdTWj0P .arrowheadPath{fill:#333333;}#mermaid-svg-Iv5EauAoYJdTWj0P .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Iv5EauAoYJdTWj0P .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Iv5EauAoYJdTWj0P .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Iv5EauAoYJdTWj0P .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Iv5EauAoYJdTWj0P .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Iv5EauAoYJdTWj0P .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Iv5EauAoYJdTWj0P .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Iv5EauAoYJdTWj0P .cluster text{fill:#333;}#mermaid-svg-Iv5EauAoYJdTWj0P .cluster span{color:#333;}#mermaid-svg-Iv5EauAoYJdTWj0P div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Iv5EauAoYJdTWj0P .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Iv5EauAoYJdTWj0P rect.text{fill:none;stroke-width:0;}#mermaid-svg-Iv5EauAoYJdTWj0P .icon-shape,#mermaid-svg-Iv5EauAoYJdTWj0P .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Iv5EauAoYJdTWj0P .icon-shape p,#mermaid-svg-Iv5EauAoYJdTWj0P .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Iv5EauAoYJdTWj0P .icon-shape rect,#mermaid-svg-Iv5EauAoYJdTWj0P .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Iv5EauAoYJdTWj0P .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Iv5EauAoYJdTWj0P .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Iv5EauAoYJdTWj0P :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 双重探测结果 M=11
0: 52
1: 空
2: 74
3: 空
4: 空
5: 空
6: 空
7: 空
8: 19
9: 30
10: 空
双重探测的分布比线性探测更均匀,但实现复杂度也更高。然而,C++ STL 的 unordered_set/unordered_map 并没有采用开放定址法,而是采用了链地址法。这是因为链地址法在处理冲突时更加灵活,且删除操作更简单。
四、链地址法:STL 哈希表的实现核心
**链地址法(Separate Chaining)**是解决哈希冲突最经典、最广泛使用的方案,也是 C++ STL 中 unordered 系列容器的底层实现方式。其核心思想是:哈希表的每个位置(桶,Bucket)不再直接存储元素,而是存储一个链表(或红黑树)的头指针。所有映射到同一位置的元素,都被链接在该位置的链表中。
4.1 数据结构示意
假设我们有以下关键字和哈希函数 h ( k e y ) = k e y % 11 h(key) = key \ \% \ 11 h(key)=key % 11:
- h ( 19 ) = 8 , h ( 30 ) = 8 , h ( 5 ) = 5 , h ( 36 ) = 3 , h ( 13 ) = 2 , h ( 20 ) = 9 , h ( 21 ) = 10 , h ( 12 ) = 1 , h ( 24 ) = 2 , h ( 96 ) = 8 h(19)=8, h(30)=8, h(5)=5, h(36)=3, h(13)=2, h(20)=9, h(21)=10, h(12)=1, h(24)=2, h(96)=8 h(19)=8,h(30)=8,h(5)=5,h(36)=3,h(13)=2,h(20)=9,h(21)=10,h(12)=1,h(24)=2,h(96)=8
#mermaid-svg-dkCfgxo4mu7o1FlJ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-dkCfgxo4mu7o1FlJ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-dkCfgxo4mu7o1FlJ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-dkCfgxo4mu7o1FlJ .error-icon{fill:#552222;}#mermaid-svg-dkCfgxo4mu7o1FlJ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-dkCfgxo4mu7o1FlJ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-dkCfgxo4mu7o1FlJ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-dkCfgxo4mu7o1FlJ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-dkCfgxo4mu7o1FlJ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-dkCfgxo4mu7o1FlJ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-dkCfgxo4mu7o1FlJ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-dkCfgxo4mu7o1FlJ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-dkCfgxo4mu7o1FlJ .marker.cross{stroke:#333333;}#mermaid-svg-dkCfgxo4mu7o1FlJ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-dkCfgxo4mu7o1FlJ p{margin:0;}#mermaid-svg-dkCfgxo4mu7o1FlJ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-dkCfgxo4mu7o1FlJ .cluster-label text{fill:#333;}#mermaid-svg-dkCfgxo4mu7o1FlJ .cluster-label span{color:#333;}#mermaid-svg-dkCfgxo4mu7o1FlJ .cluster-label span p{background-color:transparent;}#mermaid-svg-dkCfgxo4mu7o1FlJ .label text,#mermaid-svg-dkCfgxo4mu7o1FlJ span{fill:#333;color:#333;}#mermaid-svg-dkCfgxo4mu7o1FlJ .node rect,#mermaid-svg-dkCfgxo4mu7o1FlJ .node circle,#mermaid-svg-dkCfgxo4mu7o1FlJ .node ellipse,#mermaid-svg-dkCfgxo4mu7o1FlJ .node polygon,#mermaid-svg-dkCfgxo4mu7o1FlJ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-dkCfgxo4mu7o1FlJ .rough-node .label text,#mermaid-svg-dkCfgxo4mu7o1FlJ .node .label text,#mermaid-svg-dkCfgxo4mu7o1FlJ .image-shape .label,#mermaid-svg-dkCfgxo4mu7o1FlJ .icon-shape .label{text-anchor:middle;}#mermaid-svg-dkCfgxo4mu7o1FlJ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-dkCfgxo4mu7o1FlJ .rough-node .label,#mermaid-svg-dkCfgxo4mu7o1FlJ .node .label,#mermaid-svg-dkCfgxo4mu7o1FlJ .image-shape .label,#mermaid-svg-dkCfgxo4mu7o1FlJ .icon-shape .label{text-align:center;}#mermaid-svg-dkCfgxo4mu7o1FlJ .node.clickable{cursor:pointer;}#mermaid-svg-dkCfgxo4mu7o1FlJ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-dkCfgxo4mu7o1FlJ .arrowheadPath{fill:#333333;}#mermaid-svg-dkCfgxo4mu7o1FlJ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-dkCfgxo4mu7o1FlJ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-dkCfgxo4mu7o1FlJ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-dkCfgxo4mu7o1FlJ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-dkCfgxo4mu7o1FlJ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-dkCfgxo4mu7o1FlJ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-dkCfgxo4mu7o1FlJ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-dkCfgxo4mu7o1FlJ .cluster text{fill:#333;}#mermaid-svg-dkCfgxo4mu7o1FlJ .cluster span{color:#333;}#mermaid-svg-dkCfgxo4mu7o1FlJ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-dkCfgxo4mu7o1FlJ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-dkCfgxo4mu7o1FlJ rect.text{fill:none;stroke-width:0;}#mermaid-svg-dkCfgxo4mu7o1FlJ .icon-shape,#mermaid-svg-dkCfgxo4mu7o1FlJ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-dkCfgxo4mu7o1FlJ .icon-shape p,#mermaid-svg-dkCfgxo4mu7o1FlJ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-dkCfgxo4mu7o1FlJ .icon-shape rect,#mermaid-svg-dkCfgxo4mu7o1FlJ .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-dkCfgxo4mu7o1FlJ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-dkCfgxo4mu7o1FlJ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-dkCfgxo4mu7o1FlJ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 哈希桶数组 M=11
0: nullptr
1: 头指针
2: 头指针
3: 头指针
4: nullptr
5: 头指针
6: nullptr
7: nullptr
8: 头指针
9: 头指针
10: 头指针
12
13
24
36
5
96
30
19
20
21
从图中可以清晰地看到,桶 8 挂载了三个元素(19, 30, 96),桶 2 挂载了两个元素(13, 24)。每个桶内的元素通过指针链接,形成一个单链表。
4.2 插入操作:头插法的优雅
在链地址法中,插入新元素通常采用头插法 。这是因为头插法的时间复杂度为 O ( 1 ) O(1) O(1),无需遍历链表找到尾部。
cpp
size_t hashi = kv.first % _tables.size(); // 计算哈希桶位置
// 头插法
Node* newNode = new Node(kv); // 创建新节点
newNode->_next = _tables[hashi]; // 新节点指向原链表头
_tables[hashi] = newNode; // 更新桶的头指针为新节点
头插法不仅效率高,而且新插入的元素往往更有可能被近期访问(符合局部性原理),在实际应用中表现良好。
4.3 删除操作:指针的舞蹈
删除操作需要找到待删除节点的前一个节点,修改指针指向即可完成。如果删除的是头节点,则直接更新桶的头指针。由于链地址法中的节点是动态分配的,删除后可以直接释放内存,不存在开放定址法中"墓碑"标记的问题。
4.4 查找操作:遍历链表
查找时,先通过哈希函数定位到具体的桶,然后遍历该桶对应的链表,通过 == 运算符逐个比较关键字,直到找到目标或到达链表末尾。
五、扩容与重哈希:负载因子的权衡
哈希表的性能与**负载因子(Load Factor)**密切相关。负载因子定义为:
α = N M \alpha = \frac{N}{M} α=MN
其中 N N N 是表中元素个数, M M M 是桶的数量。当 α \alpha α 过大时,每个桶挂载的链表会越来越长,查找效率从 O ( 1 ) O(1) O(1) 逐渐退化为 O ( N ) O(N) O(N)。因此,当负载因子超过某个阈值(通常是 1.0)时,就必须进行扩容(Resize)和重哈希(Rehash)。
5.1 扩容的原理
扩容的核心步骤如下:
- 创建新表:申请一个更大的桶数组(通常是原大小的 2 倍)。
- 重新映射:遍历旧表的每一个桶,将其链表中的每个节点重新计算哈希位置(对新表大小取模),并挂接到新表的对应位置。
- 交换资源:将新表与旧表交换,旧表资源在作用域结束时自动释放。
注意 :扩容时不能简单地将旧数据拷贝到新数组,因为哈希值对表大小取模的结果依赖于表大小。表大小变了,元素对应的桶位置几乎一定会改变。
示例代码:
cpp
vector<Node*> newtables(_tables.size() * 2); // 新表,大小为原表2倍
for (size_t i = 0; i < _tables.size(); ++i)
{
Node* cur = _tables[i];
// 将当前桶的所有节点重新映射到新表
while (cur)
{
Node* next = cur->_next; // 先保存下一个节点
// 计算在新表中的位置
size_t hashi = cur->_kv.first % newtables.size();
// 头插到新表
cur->_next = newtables[hashi];
newtables[hashi] = cur;
cur = next; // 处理下一个节点
}
_tables[i] = nullptr; // 旧表桶置空
}
_tables.swap(newtables); // 交换新旧表
5.2 扩容后的节点分布
扩容后,原本聚集在一个桶中的元素会被分散到更多的桶中。例如,原表大小为 11,新表大小为 22。原本在桶 8 中的元素(如 19, 30, 96)在新表中可能会被映射到完全不同的位置:
- 19 % 22 = 19 19 \% 22 = 19 19%22=19
- 30 % 22 = 8 30 \% 22 = 8 30%22=8
- 96 % 22 = 8 96 \% 22 = 8 96%22=8
虽然 30 和 96 可能仍在同一桶中,但链表长度已经缩短,整体分布更加均匀。
#mermaid-svg-rhOQAVrX47hEsCMb{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-rhOQAVrX47hEsCMb .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-rhOQAVrX47hEsCMb .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-rhOQAVrX47hEsCMb .error-icon{fill:#552222;}#mermaid-svg-rhOQAVrX47hEsCMb .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-rhOQAVrX47hEsCMb .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-rhOQAVrX47hEsCMb .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-rhOQAVrX47hEsCMb .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-rhOQAVrX47hEsCMb .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-rhOQAVrX47hEsCMb .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-rhOQAVrX47hEsCMb .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-rhOQAVrX47hEsCMb .marker{fill:#333333;stroke:#333333;}#mermaid-svg-rhOQAVrX47hEsCMb .marker.cross{stroke:#333333;}#mermaid-svg-rhOQAVrX47hEsCMb svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-rhOQAVrX47hEsCMb p{margin:0;}#mermaid-svg-rhOQAVrX47hEsCMb .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-rhOQAVrX47hEsCMb .cluster-label text{fill:#333;}#mermaid-svg-rhOQAVrX47hEsCMb .cluster-label span{color:#333;}#mermaid-svg-rhOQAVrX47hEsCMb .cluster-label span p{background-color:transparent;}#mermaid-svg-rhOQAVrX47hEsCMb .label text,#mermaid-svg-rhOQAVrX47hEsCMb span{fill:#333;color:#333;}#mermaid-svg-rhOQAVrX47hEsCMb .node rect,#mermaid-svg-rhOQAVrX47hEsCMb .node circle,#mermaid-svg-rhOQAVrX47hEsCMb .node ellipse,#mermaid-svg-rhOQAVrX47hEsCMb .node polygon,#mermaid-svg-rhOQAVrX47hEsCMb .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-rhOQAVrX47hEsCMb .rough-node .label text,#mermaid-svg-rhOQAVrX47hEsCMb .node .label text,#mermaid-svg-rhOQAVrX47hEsCMb .image-shape .label,#mermaid-svg-rhOQAVrX47hEsCMb .icon-shape .label{text-anchor:middle;}#mermaid-svg-rhOQAVrX47hEsCMb .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-rhOQAVrX47hEsCMb .rough-node .label,#mermaid-svg-rhOQAVrX47hEsCMb .node .label,#mermaid-svg-rhOQAVrX47hEsCMb .image-shape .label,#mermaid-svg-rhOQAVrX47hEsCMb .icon-shape .label{text-align:center;}#mermaid-svg-rhOQAVrX47hEsCMb .node.clickable{cursor:pointer;}#mermaid-svg-rhOQAVrX47hEsCMb .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-rhOQAVrX47hEsCMb .arrowheadPath{fill:#333333;}#mermaid-svg-rhOQAVrX47hEsCMb .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-rhOQAVrX47hEsCMb .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-rhOQAVrX47hEsCMb .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rhOQAVrX47hEsCMb .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-rhOQAVrX47hEsCMb .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rhOQAVrX47hEsCMb .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-rhOQAVrX47hEsCMb .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-rhOQAVrX47hEsCMb .cluster text{fill:#333;}#mermaid-svg-rhOQAVrX47hEsCMb .cluster span{color:#333;}#mermaid-svg-rhOQAVrX47hEsCMb div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-rhOQAVrX47hEsCMb .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-rhOQAVrX47hEsCMb rect.text{fill:none;stroke-width:0;}#mermaid-svg-rhOQAVrX47hEsCMb .icon-shape,#mermaid-svg-rhOQAVrX47hEsCMb .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rhOQAVrX47hEsCMb .icon-shape p,#mermaid-svg-rhOQAVrX47hEsCMb .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-rhOQAVrX47hEsCMb .icon-shape rect,#mermaid-svg-rhOQAVrX47hEsCMb .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rhOQAVrX47hEsCMb .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-rhOQAVrX47hEsCMb .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-rhOQAVrX47hEsCMb :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 扩容后 M=22
扩容前 M=11
Rehash
Rehash
Rehash
Rehash
桶8: 96 -> 30 -> 19
桶2: 13 -> 24
桶8: 30 -> 96
桶19: 19
桶2: 24
桶13: 13
六、迭代器的封装:跨越桶的边界
哈希表的迭代器封装是一个极具技巧性的部分。与链表或红黑树的迭代器不同,哈希表的迭代器不仅要能在当前桶的链表中移动,还要能在当前桶遍历完毕后,自动跳转到下一个非空桶。
6.1 迭代器的结构
哈希表迭代器内部通常维护两个核心成员:
Node* cur:指向当前遍历到的节点。HashTable* ht:指向所属的哈希表对象,用于在需要时查找下一个非空桶。
#mermaid-svg-r4B3uzGT4ip4Mn4e{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-r4B3uzGT4ip4Mn4e .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-r4B3uzGT4ip4Mn4e .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-r4B3uzGT4ip4Mn4e .error-icon{fill:#552222;}#mermaid-svg-r4B3uzGT4ip4Mn4e .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-r4B3uzGT4ip4Mn4e .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-r4B3uzGT4ip4Mn4e .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-r4B3uzGT4ip4Mn4e .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-r4B3uzGT4ip4Mn4e .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-r4B3uzGT4ip4Mn4e .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-r4B3uzGT4ip4Mn4e .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-r4B3uzGT4ip4Mn4e .marker{fill:#333333;stroke:#333333;}#mermaid-svg-r4B3uzGT4ip4Mn4e .marker.cross{stroke:#333333;}#mermaid-svg-r4B3uzGT4ip4Mn4e svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-r4B3uzGT4ip4Mn4e p{margin:0;}#mermaid-svg-r4B3uzGT4ip4Mn4e .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-r4B3uzGT4ip4Mn4e .cluster-label text{fill:#333;}#mermaid-svg-r4B3uzGT4ip4Mn4e .cluster-label span{color:#333;}#mermaid-svg-r4B3uzGT4ip4Mn4e .cluster-label span p{background-color:transparent;}#mermaid-svg-r4B3uzGT4ip4Mn4e .label text,#mermaid-svg-r4B3uzGT4ip4Mn4e span{fill:#333;color:#333;}#mermaid-svg-r4B3uzGT4ip4Mn4e .node rect,#mermaid-svg-r4B3uzGT4ip4Mn4e .node circle,#mermaid-svg-r4B3uzGT4ip4Mn4e .node ellipse,#mermaid-svg-r4B3uzGT4ip4Mn4e .node polygon,#mermaid-svg-r4B3uzGT4ip4Mn4e .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-r4B3uzGT4ip4Mn4e .rough-node .label text,#mermaid-svg-r4B3uzGT4ip4Mn4e .node .label text,#mermaid-svg-r4B3uzGT4ip4Mn4e .image-shape .label,#mermaid-svg-r4B3uzGT4ip4Mn4e .icon-shape .label{text-anchor:middle;}#mermaid-svg-r4B3uzGT4ip4Mn4e .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-r4B3uzGT4ip4Mn4e .rough-node .label,#mermaid-svg-r4B3uzGT4ip4Mn4e .node .label,#mermaid-svg-r4B3uzGT4ip4Mn4e .image-shape .label,#mermaid-svg-r4B3uzGT4ip4Mn4e .icon-shape .label{text-align:center;}#mermaid-svg-r4B3uzGT4ip4Mn4e .node.clickable{cursor:pointer;}#mermaid-svg-r4B3uzGT4ip4Mn4e .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-r4B3uzGT4ip4Mn4e .arrowheadPath{fill:#333333;}#mermaid-svg-r4B3uzGT4ip4Mn4e .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-r4B3uzGT4ip4Mn4e .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-r4B3uzGT4ip4Mn4e .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-r4B3uzGT4ip4Mn4e .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-r4B3uzGT4ip4Mn4e .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-r4B3uzGT4ip4Mn4e .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-r4B3uzGT4ip4Mn4e .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-r4B3uzGT4ip4Mn4e .cluster text{fill:#333;}#mermaid-svg-r4B3uzGT4ip4Mn4e .cluster span{color:#333;}#mermaid-svg-r4B3uzGT4ip4Mn4e div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-r4B3uzGT4ip4Mn4e .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-r4B3uzGT4ip4Mn4e rect.text{fill:none;stroke-width:0;}#mermaid-svg-r4B3uzGT4ip4Mn4e .icon-shape,#mermaid-svg-r4B3uzGT4ip4Mn4e .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-r4B3uzGT4ip4Mn4e .icon-shape p,#mermaid-svg-r4B3uzGT4ip4Mn4e .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-r4B3uzGT4ip4Mn4e .icon-shape rect,#mermaid-svg-r4B3uzGT4ip4Mn4e .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-r4B3uzGT4ip4Mn4e .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-r4B3uzGT4ip4Mn4e .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-r4B3uzGT4ip4Mn4e :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 哈希表结构
迭代器视角
iterator
cur: 指向当前节点
ht: 指向哈希表
桶0: nullptr
桶1: nullptr
桶2: 24 -> 13
桶3: 36
节点 24
6.2 operator++ 的实现精髓
前置 ++ 操作是迭代器的核心。其逻辑如下:
- 链表内移动 :如果当前节点的
next不为空,直接移动到next。 - 跨桶跳转 :如果当前节点是链表最后一个节点(
next == nullptr),则需要从当前桶的下一个位置开始,顺序查找第一个非空桶 ,并将cur指向该桶的头节点。
cpp
template <class V, class K, class HF, class ExK, class EqK, class A>
hashtable_iterator<V, K, HF, ExK, EqK, A>&
hashtable_iterator<V, K, HF, ExK, EqK, A>::operator++()
{
const node* old = cur;
cur = cur->next; // 尝试在当前链表后移
// 如果 cur 为空,说明当前桶已遍历完毕
// 需要找下一个不为空的桶的第一个节点
if (!cur)
{
size_type bucket = ht->bkt_num(old->val); // 获取当前桶编号
while (!cur && ++bucket < ht->buckets.size())
{
cur = ht->buckets[bucket]; // 依次检查后续桶
}
}
return *this;
}
这段代码的精妙之处在于它完美地屏蔽了底层哈希桶的复杂性,对外呈现出一个线性的、连续的 遍历接口。使用者无需关心元素究竟在哪个桶中,只需像操作普通容器一样使用 ++it。
七、从哈希表到 unordered_map/unordered_set
在实现了底层哈希表之后,封装 unordered_map 和 unordered_set 的框架就水到渠成了。但这里有一个经典的设计难题需要解决:如何在同一份哈希表代码上,同时支持 set(只有 Key)和 map(Key-Value 对)的语义?
7.1 KeyOfT 的巧妙设计
STL 采用了一种称为 KeyOfT 的技巧。无论底层存储的是 Key(对于 set)还是 pair<Key, Value>(对于 map),哈希表在计算哈希值和比较相等时,都需要从中提取出 Key。
- 对于
unordered_set<T>,KeyOfT可以直接返回元素本身(因为元素就是 Key)。 - 对于
unordered_map<Key, Value>,KeyOfT则返回pair的first成员。
通过模板参数传入不同的提取仿函数,同一份哈希表代码就能适配两种容器。
7.2 实现步骤概览
一个完整的 unordered 容器实现通常包括以下步骤:
- 实现底层哈希表:包括节点结构、桶数组、哈希函数、冲突解决(链地址法)。
- 封装
unordered_map和unordered_set的框架 :通过KeyOfT解决类型差异。 - 实现普通迭代器(
iterator) :支持遍历和++操作。 - 实现常量迭代器(
const_iterator):保证只读访问。 - 处理 Key 不可修改的问题 :通过迭代器封装,确保用户无法通过迭代器修改
Key(否则会破坏哈希表的结构一致性),但允许修改Value(对于 map)。 - 实现
operator[]:对于unordered_map,提供像数组一样便捷的访问方式,若 Key 不存在则默认构造插入。
八、总结:红黑树与哈希表的抉择
通过本文的深入剖析,我们可以对 C++ STL 中的两种关联容器做一个全面的对比总结:
#mermaid-svg-EUT6mE7BXvQGsqpd{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-EUT6mE7BXvQGsqpd .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-EUT6mE7BXvQGsqpd .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-EUT6mE7BXvQGsqpd .error-icon{fill:#552222;}#mermaid-svg-EUT6mE7BXvQGsqpd .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-EUT6mE7BXvQGsqpd .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-EUT6mE7BXvQGsqpd .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-EUT6mE7BXvQGsqpd .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-EUT6mE7BXvQGsqpd .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-EUT6mE7BXvQGsqpd .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-EUT6mE7BXvQGsqpd .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-EUT6mE7BXvQGsqpd .marker{fill:#333333;stroke:#333333;}#mermaid-svg-EUT6mE7BXvQGsqpd .marker.cross{stroke:#333333;}#mermaid-svg-EUT6mE7BXvQGsqpd svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-EUT6mE7BXvQGsqpd p{margin:0;}#mermaid-svg-EUT6mE7BXvQGsqpd .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-EUT6mE7BXvQGsqpd .cluster-label text{fill:#333;}#mermaid-svg-EUT6mE7BXvQGsqpd .cluster-label span{color:#333;}#mermaid-svg-EUT6mE7BXvQGsqpd .cluster-label span p{background-color:transparent;}#mermaid-svg-EUT6mE7BXvQGsqpd .label text,#mermaid-svg-EUT6mE7BXvQGsqpd span{fill:#333;color:#333;}#mermaid-svg-EUT6mE7BXvQGsqpd .node rect,#mermaid-svg-EUT6mE7BXvQGsqpd .node circle,#mermaid-svg-EUT6mE7BXvQGsqpd .node ellipse,#mermaid-svg-EUT6mE7BXvQGsqpd .node polygon,#mermaid-svg-EUT6mE7BXvQGsqpd .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-EUT6mE7BXvQGsqpd .rough-node .label text,#mermaid-svg-EUT6mE7BXvQGsqpd .node .label text,#mermaid-svg-EUT6mE7BXvQGsqpd .image-shape .label,#mermaid-svg-EUT6mE7BXvQGsqpd .icon-shape .label{text-anchor:middle;}#mermaid-svg-EUT6mE7BXvQGsqpd .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-EUT6mE7BXvQGsqpd .rough-node .label,#mermaid-svg-EUT6mE7BXvQGsqpd .node .label,#mermaid-svg-EUT6mE7BXvQGsqpd .image-shape .label,#mermaid-svg-EUT6mE7BXvQGsqpd .icon-shape .label{text-align:center;}#mermaid-svg-EUT6mE7BXvQGsqpd .node.clickable{cursor:pointer;}#mermaid-svg-EUT6mE7BXvQGsqpd .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-EUT6mE7BXvQGsqpd .arrowheadPath{fill:#333333;}#mermaid-svg-EUT6mE7BXvQGsqpd .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-EUT6mE7BXvQGsqpd .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-EUT6mE7BXvQGsqpd .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-EUT6mE7BXvQGsqpd .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-EUT6mE7BXvQGsqpd .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-EUT6mE7BXvQGsqpd .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-EUT6mE7BXvQGsqpd .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-EUT6mE7BXvQGsqpd .cluster text{fill:#333;}#mermaid-svg-EUT6mE7BXvQGsqpd .cluster span{color:#333;}#mermaid-svg-EUT6mE7BXvQGsqpd div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-EUT6mE7BXvQGsqpd .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-EUT6mE7BXvQGsqpd rect.text{fill:none;stroke-width:0;}#mermaid-svg-EUT6mE7BXvQGsqpd .icon-shape,#mermaid-svg-EUT6mE7BXvQGsqpd .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-EUT6mE7BXvQGsqpd .icon-shape p,#mermaid-svg-EUT6mE7BXvQGsqpd .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-EUT6mE7BXvQGsqpd .icon-shape rect,#mermaid-svg-EUT6mE7BXvQGsqpd .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-EUT6mE7BXvQGsqpd .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-EUT6mE7BXvQGsqpd .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-EUT6mE7BXvQGsqpd :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 关联容器
有序容器
基于红黑树
无序容器
基于哈希表
std::set
std::map
std::unordered_set
std::unordered_map
增删查改: O(log N)
遍历: 有序
迭代器: 双向
要求: 支持 <
增删查改: 平均 O(1)
遍历: 无序
迭代器: 单向
要求: 支持哈希+==
增删查改: O(log N)
遍历: 按 Key 有序
迭代器: 双向
增删查改: 平均 O(1)
遍历: 无序
迭代器: 单向
支持 operator\[\]
| 特性 | set / map |
unordered_set / unordered_map |
|---|---|---|
| 底层结构 | 红黑树(平衡二叉搜索树) | 哈希表(链地址法) |
| 时间复杂度 | 增删查改 O ( log N ) O(\log N) O(logN) | 增删查改平均 O ( 1 ) O(1) O(1),最坏 O ( N ) O(N) O(N) |
| 遍历顺序 | 按键的升序排列 | 无序,取决于哈希函数和插入顺序 |
| 迭代器类型 | 双向迭代器 | 单向迭代器 |
| 内存占用 | 每个节点额外存储颜色、父/子指针 | 桶数组 + 链表指针,可能有负载因子冗余 |
| 适用场景 | 需要有序遍历、范围查询 | 只需要快速查找、插入、删除,不关心顺序 |
最终建议 :如果你的业务逻辑需要频繁按序遍历元素,或者需要执行
lower_bound这类范围查询操作,请选择set/map。如果你追求的是极致的查找性能,且对元素的顺序没有任何要求,那么unordered_set/unordered_map几乎是必然的选择。理解它们底层的哈希原理,不仅能帮助你写出更高效的代码,更能在遇到性能瓶颈时,从容地分析哈希冲突、负载因子,甚至定制属于自己的哈希函数。
希望这篇博客能帮助你彻底理清 unordered_set 与 unordered_map 的底层脉络。从直接定址到除留余数,从线性探测到链地址法,从重哈希到迭代器封装------每一步都是计算机科学中"用空间换时间"这一经典思想的完美体现。