开放地址法
开放地址法(Open Addressing,也称开放寻址法)是解决哈希表哈希冲突(Collision)的一种核心方法。它的核心思想是:所有元素都直接存储在哈希表数组内部,不使用任何外部数据结构(如链表)。
当插入一个新元素时,如果通过哈希函数计算出的位置已经被占用,程序不会在这个位置拉出链表,而是按照某种规则在数组中往后“探查(Probing)”,直到找到一个空的槽位(Bucket)并将其存入。
一、 开放地址法的插入逻辑
假设哈希表长度为 \(M\),插入键值对 \((key, value)\) 的步骤如下:
- 计算初始位置:计算 \(idx = h(key) \bmod M\)。
- 检查槽位:
- 如果
table[idx]为空,或者带有已删除(TOMBSTONE)标记,直接将元素写入该位置,插入成功。 - 如果
table[idx]已有其他元素(发生冲突),则进入探查流程。
- 计算下一个位置:根据预设的探查公式计算 \(idx_{next}\),重复步骤 2,直到找到空位置或遍历完整个表(触发扩容)。
二、 常见的 3 种探查规则
假设初始哈希位置为 \(h_0 = h(key) \bmod M\),第 \(i\) 次探查的位置公式 \(h_i\) 如下:
-
1. 线性探查(Linear Probing)
-
公式:\(h_i = (h_0 + i) \bmod M\)
-
逻辑:冲突后依次查看下一个位置(\(h_0+1, h_0+2, h_0+3, \dots\))。
-
优缺点:缓存命中率高,CPU Cache 非常友好;但容易产生主聚集(Primary Clustering),即连续的被占用槽位会越来越长,严重影响性能。
-
2. 二次探查 / 平方探查(Quadratic Probing)
-
公式:\(h_i = (h_0 + c_1 \cdot i + c_2 \cdot i^2) \bmod M\) (如步长依次为 \(+1^2, -1^2, +2^2, -2^2, \dots\))
-
逻辑:冲突后按步长的平方跳跃探查。
-
优缺点:有效减缓了主聚集现象;但探查路径固定,可能产生次聚集(Secondary Clustering),且需要谨慎设计数组大小(通常要求为素数)以保证能遍历整个表。
-
3. 双重哈希(Double Hashing)
-
公式:\(h_i = (h_0 + i \cdot h_2(key)) \bmod M\)
-
逻辑:使用第二个独立哈希函数 \(h_2(key)\) 来决定探查的步长。
-
优缺点:探查步长与键值相关,随机性最好,几乎不会发生聚集现象;但计算开销略大。
三、 开放地址法 vs 链地址法(Chaining)
| 对比维度 | 开放地址法 (Open Addressing) | 链地址法 (Chaining) |
|---|---|---|
| 存储结构 | 仅使用单一连续数组 | 数组 + 链表/红黑树 |
| 装载因子 (\(\alpha = \frac{N}{M}\)) | 必须满足 \(\alpha < 1\)(通常超过 0.7 就要扩容) | 可以 \(\ge 1\) |
| 内存与 Cache | 连续内存,CPU 缓存极其友好,无需额外指针开销 | 频繁分配节点内存,产生内存碎片,Cache 命中率低 |
| 删除操作 | 较复杂,不能真正置空(需打上墓碑标记,避免断链) | 简单,直接从链表中移除节点 |
在 Python 的 dict、Rust 的 HashMap 以及高性能高性能 C++ 容器(如 Google absl::flat_hash_map)中,通常都优先采用开放地址法(或其变体如 Robin Hood Hashing / Swiss Tables)以追求极致的内存局部性和 CPU Cache 效率。

浙公网安备 33010602011771号