哈希表概述
哈希表(Hash Table)是 C++ STL 中 unordered_set 和 unordered_map 的基础数据结构。其核心目的是通过哈希函数将任意长度的输入数据映射为固定长度的输出值,从而实现快速的数据查找、存储和比较。
1. 哈希函数的设计目标
哈希函数是哈希表的核心组成部分,主要承担将键(Key)映射到存储位置的任务。一个优秀的哈希函数应具备以下特点:
- 确定性:同一输入始终映射到同一个哈希值。
- 压缩性:无论输入长度如何,输出长度固定。
- 高效性:计算过程应快速,时间复杂度通常为 O(1) 或 O(k)。
- 均匀分布:理想情况下,不同键应均匀映射到各个位置,避免冲突集中。
常见哈希函数算法
-
直接定址法 公式:
H(key) = key或H(key) = a × key + b。 适用于关键字范围较小且连续的场景,如存储年龄或月份。优点是简单高效无冲突,缺点是空间浪费大。 -
除法散列法 公式:
H(key) = key % m。 最经典的方法,利用取余运算将大范围关键字映射到[0, m-1]区间。关键优化在于m的选取:优先选择质数,避免使用 2 的幂次或 10 的幂次,以减少低位相同导致的冲突。 -
乘法散列法 公式:
h(key) = floor(m * (key * A mod 1))。 其中A为(0, 1)之间的常数,常取黄金分割数 0.6180339887。该方法对哈希表大小m的要求较灵活,分布均匀性较好。 -
全域散列法 从精心设计的哈希函数族中随机选择一个函数。数学上保证任意两个不同关键字冲突的概率不超过
1/m,能有效应对最坏情况下的输入。
2. 负载因子与扩容
负载因子(Load Factor)定义为 λ = n / m,即元素数量与哈希表容量的比值。它是性能与内存利用率的平衡器:
- λ 越小:冲突概率低,操作接近 O(1),但内存浪费严重。
- λ 越大:内存利用率高,但冲突增加,性能退化至 O(n)。
当负载因子超过阈值(通常设为 0.7)时,触发扩容机制:新建更大的桶数组(通常为原容量 2 倍或下一个质数),重新计算所有元素的哈希值并迁移到新表中。
3. 哈希冲突处理
由于输入空间远大于输出空间,冲突无法完全避免。主流处理方式有两种:
开放定址法(Open Addressing)
所有元素存储在数组本身中,发生冲突时按探测序列寻找空位。
- 线性探测:
h_i(key) = (h(key) + i) % m。简单但易产生'聚集'现象,导致后续插入效率降低。 - 二次探测:
h_i(key) = (h(key) + c1*i + c2*i^2) % m。减少聚集,但不能探测所有位置。 - 双重散列:
h_i(key) = (h1(key) + i*h2(key)) % m。使用两个哈希函数,探测序列更均匀,需确保h2(key)与表长互质。
删除注意事项:在开放定址法中,不能直接清空位置,否则会影响后续冲突元素的查找。应标记为 DELETE 状态,仅在遇到 EMPTY 时停止探测。


