Archipelago

Lec 8随机化:通用哈希与完美哈希

讲义完成更新于 2026-09-17

Lec 8 随机化:通用哈希与完美哈希(Randomization: Universal & Perfect Hashing)

TL;DR

  • 打破简单均匀假设:传统哈希依赖“输入数据完全随机分布”的不切实际假设,通用哈希通过在运行时由算法随机挑选哈希函数,将随机性掌控在算法自身手中以防御对抗性输入。
  • 通用哈希族定义与界限:若函数族 H\mathcal{H} 满足任意两不同键发生冲突的概率 ≤1/m\le 1/m,则任意槽的期望冲突数被严格限制在 1+α1 + \alpha(装填因子 α=n/m\alpha = n/m)。
  • 两级完美哈希(FKS 方案):第一级哈希将 nn 个键分散到 m=nm = n 个桶中,第二级为包含 njn_j 个键的桶分配 mj=nj2m_j = n_j^2 大小的二次方表,利用生日悖论消除冲突。
  • 最坏 O(1)O(1) 查找与线性空间:通过马尔可夫不等式严格证明二级散列表的总空间期望为 E[∑nj2]<2n\mathbb{E}[\sum n_j^2] < 2n,在最坏情况下仅需 2 次哈希计算即可完成确定性无冲突检索。

字典 ADT 与简单均匀哈希的缺陷

字典(Dictionary)是一种基础抽象数据类型(ADT),用于维护一组动态元素集合,支持以下核心操作:

  • Insert(x):插入元素 xx
  • Delete(x):删除指定元素 xx
  • Search(k):检索键为 kk 的元素

设关键字全集为 U={0,1,…,u−1}U = \{0, 1, \dots, u-1\},当前集合包含 nn 个键,散列表大小为 mm。

哈希表链地址法冲突处理示意图

在初等算法教学中,通常引入简单均匀散列假设(Simple Uniform Hashing Assumption, SUHA):假设任意关键字等可能地散列到 mm 个槽中的每一个,且各关键字的散列彼此独立。在此假设下,采用链地址法解决冲突时,槽的平均链长为装填因子 α=n/m\alpha = n/m,单次查找的期望耗时为 Θ(1+α)\Theta(1 + \alpha)。

然而在现实对抗环境中,SUHA 存在致命漏洞:若攻击者获知了固定的哈希函数 hh,便可专门构造一组全部映射到同一槽位 h(k1)=h(k2)=…h(k_1) = h(k_2) = \dots 的对抗样本,导致查找时间退化至最坏情况下的 Θ(n)\Theta(n),引发哈希拒绝服务攻击(Hash DoS)。


通用哈希(Universal Hashing)及其严格期望界

解决对抗性攻击的根本途径是将随机性交由算法内部掌管:在算法启动时,从一组预先设计好的哈希函数族 H\mathcal{H} 中,以均匀分布独立随机抽取一个哈希函数 hh。此时即使攻击者预先知晓输入数据,由于不知道算法当前抽取的是哪个具体函数,依然无法诱发最坏情况。

定义

通用哈希函数族(Universal Hash Family) 设 H\mathcal{H} 是将全集 UU 映射至 {0,1,…,m−1}\{0, 1, \dots, m-1\} 的有限哈希函数集合。若对于 UU 中任意两个互异的关键字 k1≠k2k_1 \neq k_2,随机选取的函数 h∈Hh \in \mathcal{H} 发生冲突的概率满足:

Pr⁡h∈H[h(k1)=h(k2)]≤1m\Pr_{h \in \mathcal{H}} [h(k_1) = h(k_2)] \le \frac{1}{m}

则称 H\mathcal{H} 为通用哈希函数族。

冲突期望的严格证明

定理

通用哈希链长界 设 hh 从通用函数族 H\mathcal{H} 中均匀随机选出,并将任意包含 nn 个不同键的集合 SS 散列至大小为 mm 的表中。对于任意给定键 k∈Uk \in U(无论 kk 是否在 SS 中),包含 kk 的槽内来自 SS 的期望键数满足:

E[nh(k)]≤{α(k∉S)1+α(k∈S)(其中 α=nm)\mathbb{E}[n_{h(k)}] \le \begin{cases} \alpha & (k \notin S) \\ 1 + \alpha & (k \in S) \end{cases} \quad \left(\text{其中 } \alpha = \frac{n}{m}\right)

证明: 对任意 x∈Sx \in S,定义指示随机变量 Ik,x∈{0,1}I_{k, x} \in \{0, 1\} 表示 kk 与 xx 是否发生冲突:

Ik,x=1{h(k)=h(x)}I_{k, x} = \mathbf{1}_{\{h(k) = h(x)\}}

由通用哈希定义,对于所有 x≠kx \neq k:

E[Ik,x]=Pr⁡[h(k)=h(x)]≤1m\mathbb{E}[I_{k, x}] = \Pr[h(k) = h(x)] \le \frac{1}{m}

与键 kk 同槽的集合 SS 中元素总数可表示为:

nh(k)=∑x∈SIk,xn_{h(k)} = \sum_{x \in S} I_{k, x}

由期望的线性性质(Linearity of Expectation):

  • 若 k∉Sk \notin S,所有 x∈Sx \in S 均与 kk 互异: E[nh(k)]=∑x∈SE[Ik,x]≤∑x∈S1m=nm=α\mathbb{E}[n_{h(k)}] = \sum_{x \in S} \mathbb{E}[I_{k, x}] \le \sum_{x \in S} \frac{1}{m} = \frac{n}{m} = \alpha
  • 若 k∈Sk \in S,分解出自身项 x=kx = k(此时必有 Ik,k=1I_{k, k} = 1): E[nh(k)]=E[Ik,k]+∑x∈S∖{k}E[Ik,x]≤1+n−1m<1+α\mathbb{E}[n_{h(k)}] = \mathbb{E}[I_{k, k}] + \sum_{x \in S \setminus \{k\}} \mathbb{E}[I_{k, x}] \le 1 + \frac{n - 1}{m} < 1 + \alpha

证毕。只要维护 m=Θ(n)m = \Theta(n),所有操作的期望时间严格受限于 O(1)O(1)。


构造通用哈希函数族

如何高效构造满足通用性的哈希族?Carter 与 Wegman 给出了基于有限域乘法与模运算的经典方案:

  1. 选取一个足够大的素数 pp,使得 p>up > u(全集中的每个键均满足 0≤k<p0 \le k < p);
  2. 令 Zp={0,1,…,p−1}\mathbb{Z}_p = \{0, 1, \dots, p-1\},Zp∗={1,2,…,p−1}\mathbb{Z}_p^* = \{1, 2, \dots, p-1\};
  3. 对于任意 a∈Zp∗a \in \mathbb{Z}_p^* 与 b∈Zpb \in \mathbb{Z}_p,定义哈希函数 ha,bh_{a, b}: ha,b(k)=((ak+b) mod p) mod mh_{a, b}(k) = \big( (ak + b) \bmod p \big) \bmod m
  4. 函数族 Hp,m={ha,b∣a∈Zp∗, b∈Zp}\mathcal{H}_{p, m} = \{h_{a, b} \mid a \in \mathbb{Z}_p^*, \, b \in \mathbb{Z}_p\}。

定理

函数族 Hp,m\mathcal{H}_{p, m} 是一个通用哈希函数族,其包含 p(p−1)p(p-1) 个不同的哈希函数。

证明思路: 对两个互异键 k1≠k2k_1 \neq k_2,线性映射 r=(ak1+b) mod pr = (a k_1 + b) \bmod p 与 s=(ak2+b) mod ps = (a k_2 + b) \bmod p 在模 pp 的有限域上构成双射。两式相减得 r−s≡a(k1−k2)(modp)r - s \equiv a(k_1 - k_2) \pmod p。由于 pp 是素数且 k1≠k2,a≠0k_1 \neq k_2, a \neq 0,故 r≠sr \neq s。 当 (a,b)(a, b) 均匀遍历所有可能时,(r,s)(r, s) 等概率地遍历所有不同的有序对。 进一步对 mm 取模时,对于固定的 rr,在剩余的 p−1p-1 个 ss 中,满足 r≡s(modm)r \equiv s \pmod m 的取值个数至多为 ⌈p/m⌉−1≤(p−1)/m\lceil p/m \rceil - 1 \le (p-1)/m。因此碰撞概率 ≤(p−1)/mp−1=1m\le \frac{(p-1)/m}{p-1} = \frac{1}{m}。


两级完美哈希(FKS 方案)与最坏 O(1)O(1) 查找

通用哈希仅保证期望单次 O(1)O(1),但在最坏情况下单槽冲突仍可能较大。对于静态字典(如编程语言保留字检索、光盘固定字典、只读知识库索引),Fredman、Komlós 与 Szemerédi 于 1984 年提出了划时代的 FKS 两级完美哈希方案,实现最坏情况下 O(1)O(1) 查找与线性 O(n)O(n) 空间消耗。

两级体系架构设计

  1. 第一级(主表):
    • 选取容量为 m=nm = n 的主散列表,使用通用哈希函数 hh 将 nn 个键分发至 mm 个槽;
    • 设落入槽 jj 的键数为 njn_j。
  2. 第二级(二级子表):
    • 为每个非空槽 jj 分配一个独立的二级散列表 SjS_j,其容量设为平方规模: mj=nj2m_j = n_j^2
    • 从二级通用哈希族中随机挑选一个函数 hjh_j,将槽 jj 内的 njn_j 个键散列至大小为 nj2n_j^2 的二级表中,直到没有任何冲突发生为止。

为什么 mj=nj2m_j = n_j^2 能保证无冲突?

定理

生日悖论与二次空间零冲突 若将 njn_j 个键通过通用哈希映射至大小为 mj=nj2m_j = n_j^2 的表中,发生至少一次冲突的概率严格小于 1/21/2。

证明: 在包含 njn_j 个键的集合中,可能发生冲突的键对总数为 (nj2)=nj(nj−1)2\binom{n_j}{2} = \frac{n_j(n_j - 1)}{2}。 设指示变量 Cx,y=1{hj(x)=hj(y)}C_{x, y} = \mathbf{1}_{\{h_j(x) = h_j(y)\}},由通用哈希性质,E[Cx,y]≤1/mj=1/nj2\mathbb{E}[C_{x, y}] \le 1/m_j = 1/n_j^2。 冲突对总数 CC 的数学期望为:

E[C]=∑x<yE[Cx,y]≤(nj2)1nj2=nj(nj−1)2nj2<12\mathbb{E}[C] = \sum_{x < y} \mathbb{E}[C_{x, y}] \le \binom{n_j}{2} \frac{1}{n_j^2} = \frac{n_j(n_j - 1)}{2 n_j^2} < \frac{1}{2}

应用马尔可夫不等式(Markov's Inequality):

Pr⁡[C≥1]≤E[C]1<12\Pr[C \ge 1] \le \frac{\mathbb{E}[C]}{1} < \frac{1}{2}

因此,只要随机抽取二级哈希函数 hjh_j,至少有 1/21/2 的概率实现完全零冲突!若发生冲突,只需重新随机生成 hjh_j 再次尝试,期望尝试次数不超过 2 次即可找到完美的二级哈希函数。

全局空间消耗的线性约束

总存储空间为 ∑j=0m−1mj=∑j=0m−1nj2\sum_{j=0}^{m-1} m_j = \sum_{j=0}^{m-1} n_j^2。二级分配平方空间是否会导致总空间爆炸?

定理

全局空间期望界 当第一级散列表大小取 m=nm = n 时,二级子表空间总和的期望值满足:

E[∑j=0m−1nj2]<2n\mathbb{E}\left[ \sum_{j=0}^{m-1} n_j^2 \right] < 2n

证明: 利用恒等式 nj2=nj+2(nj2)n_j^2 = n_j + 2 \binom{n_j}{2}:

∑j=0m−1nj2=∑j=0m−1nj+2∑j=0m−1(nj2)=n+2∑j=0m−1∑x<y1{h(x)=h(y)=j}=n+2∑x<y1{h(x)=h(y)}\sum_{j=0}^{m-1} n_j^2 = \sum_{j=0}^{m-1} n_j + 2 \sum_{j=0}^{m-1} \binom{n_j}{2} = n + 2 \sum_{j=0}^{m-1} \sum_{x < y} \mathbf{1}_{\{h(x) = h(y) = j\}} = n + 2 \sum_{x < y} \mathbf{1}_{\{h(x) = h(y)\}}

两边取期望:

E[∑j=0m−1nj2]=n+2∑x<yPr⁡[h(x)=h(y)]≤n+2(n2)1m=n+2n(n−1)2n=n+(n−1)<2n\mathbb{E}\left[ \sum_{j=0}^{m-1} n_j^2 \right] = n + 2 \sum_{x < y} \Pr[h(x) = h(y)] \le n + 2 \binom{n}{2} \frac{1}{m} = n + 2 \frac{n(n-1)}{2n} = n + (n - 1) < 2n

再次应用马尔可夫不等式:

Pr⁡[∑j=0m−1nj2≥4n]≤E[∑nj2]4n<2n4n=12\Pr\left[ \sum_{j=0}^{m-1} n_j^2 \ge 4n \right] \le \frac{\mathbb{E}[\sum n_j^2]}{4n} < \frac{2n}{4n} = \frac{1}{2}

因此,第一级哈希建表时,总空间受控在 4n4n 以内的概率大于 1/21/2。若超过 4n4n,直接重选第一级哈希函数重构即可,期望重构次数至多 2 次。

检索性能

查询任意键 kk:

  1. 计算 j=h(k)j = h(k) 定位二级子表;
  2. 计算 pos=hj(k)pos = h_j(k) 读取槽内键值;
  3. 检查 Sj[pos]S_j[pos] 是否等于 kk。

全过程包含且仅包含 2 次算术哈希计算与 2 次内存间接寻址,无循环、无链表遍历、无分支重试,在最坏情况下确定性耗时严格为 O(1)O(1),总空间复杂度严格为 O(n)O(n)。


我的理解

通用哈希与完美哈希是“算法博弈论”与“概率分层”的巅峰之作:

  1. 从被动假设到主动打破:经典算法往往假设“输入服从良好分布”,在现实对抗中不堪一击。通用哈希反客为主,将随机源收拢于算法内部。它告诉我们:最强大的确定性往往源于算法主动引入的不可预测性。
  2. 两级架构的宏微解耦:如果对全量 nn 个元素直接采用单一无冲突表,由生日悖论需要 O(n2)O(n^2) 空间,代价不可承受。FKS 巧妙地利用第一级将规模拆解至微观桶,使得宏观期望满足线性 ∑nj2<2n\sum n_j^2 < 2n。这是一种“宏观用期望平摊、微观用平方防碰撞”的精妙解耦。
  3. 确定性无冲突的工业价值:在现代高性能网关查找、编译器符号表解析与密码学字典验证中,消除“长尾延迟抖动”往往比单纯提高平均速度更为关键。FKS 证明了:即使在完全静态的哈希查找中,我们也可以彻底消灭冲突链,将运行时间锁定在硬实时常数内。 :::