哈希表概述
哈希表(Hash Table)是 C++ STL 中 unordered_map 和 unordered_set 的底层核心数据结构。它通过哈希函数将键映射到固定长度的输出值,从而实现接近 O(1) 时间复杂度的查找、插入和删除操作。理解哈希表的内部机制,对于掌握高性能容器及编写自定义数据结构至关重要。
核心概念
哈希函数
哈希函数的作用是将任意长度的输入数据(键)映射为固定长度的哈希值。一个优秀的哈希函数应具备以下特点:
- 确定性:相同的输入始终产生相同的输出。
- 均匀分布:不同的键应尽可能均匀地分布在哈希表中,减少冲突。
- 高效性:计算过程应快速,避免成为性能瓶颈。
常见的哈希函数构造方法包括:
直接定址法
直接使用关键字本身或其线性函数作为地址。公式为 H(key) = key 或 H(key) = a × key + b。适用于关键字范围较小且连续的场景,如存储年龄或月份。
除法散列法
最常用的方法,利用取余运算将关键字映射到 [0, m-1] 区间。公式为 H(key) = key % m。
关键点:模数 m 的选择直接影响性能。通常建议选取质数,以避免关键字分布不均导致的冲突。例如,若 m=10,偶数关键字都会映射到偶数位置;若 m=11,分布则更分散。
乘法散列法
先将关键字乘以一个常数 A (0 < A < 1),取小数部分后乘以表长 m 并向下取整。公式为 h(key) = floor(m * (key * A mod 1))。该方法对 m 的取值限制较少,通常使用黄金分割比作为 A。
全域散列法
从一组哈希函数族中随机选择一个函数。这能确保即使面对恶意构造的最坏情况输入,也能在平均意义上保持良好性能。
负载因子
负载因子(Load Factor)定义为 λ = n / m,其中 n 是元素数量,m 是桶的数量。它是衡量哈希表填充程度的指标。
- λ 过小:空间浪费严重,但冲突少。
- λ 过大:内存利用率高,但冲突概率激增,性能退化至 O(n)。
当负载因子超过阈值(通常为 0.7 或 1.0)时,需要触发扩容(Resize)。扩容流程包括:创建更大的新数组、重新计算所有元素的哈希值并迁移到新表、释放旧内存。
哈希冲突
由于输入空间远大于输出空间,不同键映射到同一位置的情况无法避免。处理冲突主要有两种策略:开放定址法和链地址法。
冲突处理策略
开放定址法
所有元素都存储在哈希表数组本身中。发生冲突时,按探测序列寻找下一个空闲位置。
线性探测
探测公式:h_i(key) = (h(key) + i) % m。
优点是实现简单,但容易产生'聚集'现象,即连续多个位置被占用,导致后续插入效率降低。
二次探测
探测公式:h_i(key) = (h(key) + c1 * i + c2 * i^2) % m。
相比线性探测,二次探测能减少聚集,但不能保证探测到所有位置,需满足特定条件(如表长为 4k+1 形式的质数)。
双重散列
探测公式:h_i(key) = (h1(key) + i * h2(key)) % m。


