哈希表与通用哈希

哈希表与通用哈希

exdoubled Lv5

数据结构与符号表问题

数据结构的核心作用:封装数据,支持特定操作,我们关注操作的效率

符号表 \(T\) 存储 \(n\) 条记录,每条记录 \(x\) 包含键 \(\text{key}[x]\) 和卫星数据

需要支持的操作: - \(\text{INSERT}(T, x)\):将记录 \(x\) 插入到表 \(T\) 中 - \(\text{DELETE}(T, x)\):从表 \(T\) 中删除记录 \(x\) - \(\text{SEARCH}(T, k)\):在表 \(T\) 中查找键为 \(k\) 的记录


直接访问表

假设键集合 \(K \subseteq \{0, 1, \ldots, m-1\}\),且所有键互不相同

建立数组 \(T[0 \ldots m-1]\)

\[T[k] = \begin{cases} x & \text{如果 } k \in K \text{ 且 } \text{key}[x] = k \\ \text{NIL} & \text{否则} \end{cases}\]

所有操作 \(\Theta(1)\)

问题:键的范围可能非常大,64 位整数有 \(2^{64}\) 种可能的键,字符串更多,无法为每个可能的键分配一个槽位


哈希函数与哈希表

使用哈希函数 \(h: U \rightarrow \{0, 1, \ldots, m-1\}\) 将全域 \(U\) 映射到表中,\(m \ll |U|\)

冲突:当一条待插入的记录映射到已被占据的槽位时发生

由于\(|U| > m\),由鸽巢原理知,冲突不可避免

链接法解决冲突

将映射到同一槽位的所有记录链接成链表

1
2
3
4
5
T[0] → [记录] → [记录] → NIL
T[1] → NIL
T[2] → [记录] → NIL
...
T[i] → [49] → [86] → [52] → NIL (h(49) = h(86) = h(52) = i)

链接法的分析

简单均匀哈希假设 (SUHA):每个键被哈希到表的每个槽位的概率相等,且独立

装载因子\(\alpha = \frac{n}{m}\)(每个槽位中平均键的数量)

球入箱模型:将 \(n\) 个球随机均匀扔入 \(m\) 个箱子,所有投掷相互独立

查找一个给定键的期望时间:

\[\text{期望查找时间} = \Theta(1 + \alpha)\]

\(\Theta(1)\) 计算哈希函数并访问槽位,\(\alpha\) 搜索链表的期望代价

\(\alpha = O(1)\)(即 \(n = O(m)\)),期望查找时间为 \(\Theta(1)\)


选择好的哈希函数

好的哈希函数应当将键均匀分布到各槽位,键分布中的规律性不应影响均匀性

乘法方法

假设键为整数,\(m = 2^r\),字长 \(w\)

\[ h(k) = (A \cdot k \mod 2^w) \text{ rsh } (w - r) \]

  • \(A\) 是奇整数,\(2^{w-1} < A < 2^w\)
  • \(\text{rsh}\) 为右移运算符

不要选太接近 \(2^w\)\(A\)

示例\(m = 8 = 2^3\)\(w = 7\)

1
2
3
4
A = 1011001 (二进制)
k = 1101011 (二进制)
A × k = 10010100 1100 11 (14位)
取高 r=3 位 → h(k)

点积方法

\(m\) 为素数,将键 \(k\) 分解为 \(r+1\)\(k = \langle k_0, k_1, \ldots, k_r \rangle\)\(0 \leq k_i < m\)

随机选择 \(a = \langle a_0, a_1, \ldots, a_r \rangle\)

\[ h_a(k) = \left(\sum_{i=0}^{r} a_i k_i\right) \mod m \]

实践中表现优异,但计算代价较高

制表哈希

Patrascu-Thorup (2012) 提出,将键分解为若干字节,每个字节通过查表获取随机值,然后异或

\[ h(k) = h_1[k_1] \oplus h_2[k_2] \oplus h_3[k_3] \oplus h_4[k_4] \]

\(h_i\) 存储在小型查找表中,只需几次查表和异或,实践中非常高效,也可用于哈希字符串


通用哈希

固定哈希函数的弱点

对于任何固定的哈希函数 \(h\),总存在一组键使得所有键映射到同一槽位

即可以选择所有键来自 \(\{k \in U | h(k) = i\}\)

解决方案:随机选择哈希函数,独立于键的选择

通用哈希的定义

\(\mathcal{H}\) 为有限哈希函数集合,如果对于所有 \(x, y \in U\)\(x \neq y\)

\[ \Pr_{h \in \mathcal{H}}[h(x) = h(y)] \leq \frac{1}{m} \]

则称 \(\mathcal{H}\) 是通用的

等价表述: \[ |\{h \in \mathcal{H} | h(x) = h(y)\}| \leq \frac{|\mathcal{H}|}{m} \]

通用哈希的示例

\(\{a, b\}\) 映射到 \(\{0, 1\}\)

通用

\(h_1: a\to0, b\to0\)

\(h_2: a\to0, b\to1\)

\(h_3: a\to1, b\to0\)

\(h_4: a\to1, b\to1\)

\(h(a) = h(b)\) 的有 \(h_1, h_4\)

\(\frac{2}{4} = \frac{1}{2} = \frac{1}{m}\)

非通用\(h_1: a\to0, b\to0\)

\(h_2: a\to1, b\to1\)

\(h(a) = h(b)\) 的有 \(h_1, h_2\)

\(\frac{2}{2} = 1 > \frac{1}{m}\)

通用哈希的性质

定理:设 \(h\) 从通用哈希函数集 \(\mathcal{H}\) 中均匀随机选取,将 \(n\) 个键哈希到大小 \(m\) 的表中,对于给定键 \(x\)\[ E[\text{与 } x \text{ 冲突的次数}] < \frac{n}{m} \]

点击查看证明

定义指示器随机变量 \[ c_{xy} = \begin{cases} 1 & \text{如果 } h(x) = h(y) \\ 0 & \text{否则} \end{cases} \]

\(E[c_{xy}] = \Pr\{h(x) = h(y)\} \leq \frac{1}{m}\)(通用性)

\(C_x = \sum_{y \in T - \{x\}} c_{xy}\)

\[ E[C_x] = \sum_{y \in T - \{x\}} E[c_{xy}] \leq \sum_{y \in T - \{x\}} \frac{1}{m} = \frac{n - 1}{m} < \frac{n}{m} \]

构造通用哈希函数族

点积哈希函数族

\(m\) 为素数,\(h_a(k) = (\sum_{i=0}^{r} a_i k_i) \mod m\)

\(\mathcal{H} = \{h_a\}\) 大小为 \(m^{r+1}\)

点击查看通用性证明

\(x \neq y\) 在位置 0 上不同(\(x_0 \neq y_0\)

\(h_a(x) = h_a(y)\) 等价于

\[\sum_{i=0}^{r} a_i (x_i - y_i) \equiv 0 \pmod{m}\]

\[a_0(x_0 - y_0) \equiv -\sum_{i=1}^{r} a_i(x_i - y_i) \pmod{m}\]

已知 \(m\) 为素数时,对任何 \(z \in \mathbb{Z}_m\)\(z \neq 0\)),存在唯一 \(z^{-1}\) 使得 \(z \cdot z^{-1} \equiv 1 \pmod{m}\)

由于 \(x_0 \neq y_0\)\((x_0 - y_0)\) 的逆存在,则:

\[ a_0 \equiv -(x_0 - y_0)^{-1} \sum_{i=1}^{r} a_i(x_i - y_i) \pmod{m} \]\(a_1, \ldots, a_r\) 的任何选择,恰好有一个 \(a_0\) 使得 \(x\)\(y\) 冲突

导致冲突的 \(h_a\) 数量为 \(m^r = \frac{|\mathcal{H}|}{m}\),满足通用性定义

Carter-Wegman 通用哈希族 (1979)

\[h_{a,b}(k) = ((ak + b) \mod p) \mod m\]

  • \(p\) 是大于所有键值的素数
  • \(a \in \{1, 2, \ldots, p-1\}\)\(b \in \{0, 1, \ldots, p-1\}\)
  • \(m\) 可以是任意正整数

\(k \neq l\),映射 \((k, l) \mapsto (ak + b \mod p, al + b \mod p)\) 是双射,冲突概率 \(\leq 1/m\)

优点:只需两个数 \(a, b\) 表示一个函数,实现简单


完美哈希

给定 \(n\) 个键的静态集合,构造大小 \(m = O(n)\) 的哈希表,SEARCH 最坏 \(\Theta(1)\)

两级方案

核心思想:两级都使用通用哈希

第一级:用 \(h\)\(n\) 个键映射到 \(m\) 个槽位(\(m = O(n)\)

第二级:对每个槽位 \(j\)(有 \(n_j\) 个键),用大小 \(m_j = n_j^2\) 的二级表和独立的通用哈希函数,第二级没有冲突

示例

键集合 \(\{31, 14, 27, 26, 9, 86, 40, 37, 22\}\)\(m = 9\)

1
2
3
T[1]: S₁ → {14, 27},m₁ = 4
T[4]: S₄ → {26},m₄ = 1
T[6]: S₆ → {9, 86, 40, 37, 22},m₆ = 25,无冲突

第二级无冲突的保证

定理:设 \(\mathcal{H}\) 是通用哈希函数类,表大小 \(m = n^2\)。若用随机 \(h \in \mathcal{H}\)\(n\) 个键哈希到表中,期望冲突数最多 \(\frac{1}{2}\)

点击查看证明

由通用性,任意两个键冲突概率为 \(\frac{1}{m} = \frac{1}{n^2}\)

可能冲突的键对数量为 \(\binom{n}{2}\),期望冲突数为:

\[ \binom{n}{2} \cdot \frac{1}{n^2} = \frac{n(n-1)}{2n^2} = \frac{n-1}{2n} < \frac{1}{2} \]

由 Markov 不等式:\(\Pr\{X \geq 1\} \leq E[X] < \frac{1}{2}\)

因此无冲突概率 \(> \frac{1}{2}\),期望只需测试 2 次即可找到无冲突的函数

存储空间分析

定理:将 \(n\) 个键存在大小 \(m = n\) 的一级表中,使用从通用类中随机选择的哈希函数 \(h\),则: \[ E\left[\sum_{j=0}^{m-1} n_j^2\right] < 2n \]

点击查看证明

\[ \sum_{j=0}^{m-1} n_j^2 = \sum_{j=0}^{m-1} (n_j + 2\binom{n_j}{2}) = n + 2 \cdot (\text{冲突总数}) \]

冲突总数期望 \(\leq \binom{n}{2} \cdot \frac{1}{m} = \frac{n(n-1)}{2n} = \frac{n-1}{2}\)

\[ E\left[\sum n_j^2\right] \leq n + 2 \cdot \frac{n-1}{2} = 2n - 1 < 2n \]

二级表总大小期望 \(< 2n\),完美哈希总空间 \(O(n)\)

完美哈希的构造

  1. 找好的一级哈希函数:从通用族随机选择 \(h\),检查 \(\sum n_j^2 < 4n\),期望约一半函数满足,期望尝试 2 次
  2. 对每个桶构造无冲突的二级哈希函数:大小 \(n_j^2\) 的表,随机选择 \(h_j\),无冲突概率 \(\geq 1/2\),期望尝试 2 次

总构造时间 \(O(n)\) 期望


开放寻址

不使用哈希表以外的额外存储空间,插入时系统地探测表中槽位直到找到空槽

哈希函数依赖键和探测序号:\(h: U \times \{0, 1, \ldots, m-1\} \rightarrow \{0, 1, \ldots, m-1\}\)

探测序列 \(\langle h(k,0), h(k,1), \ldots, h(k,m-1) \rangle\) 应该是 \(\{0, 1, \ldots, m-1\}\) 的一个排列

插入示例

1
2
3
4
插入 k = 496:
步骤 0: 探测 h(496, 0) → 碰到 204,冲突
步骤 1: 探测 h(496, 1) → 碰到 586,冲突
步骤 2: 探测 h(496, 2) → 找到空槽,插入

查找使用相同的探测序列,找到目标键则成功,遇到空槽则失败

探测策略

线性探测

\[ h(k, i) = (h'(k) + i) \mod m \]

实现简单,但存在一次聚集问题:已占用槽位形成的长连续段倾向于变得更长(长度为 \(t\) 的段被新元素”击中”概率正比于 \(t\),导致正反馈)

双重哈希

\[ h(k, i) = (h_1(k) + i \cdot h_2(k)) \mod m \]

通常产生很好的结果,\(h_2(k)\) 必须与 \(m\) 互素

一种方法是让 \(m\) 为 2 的幂,\(h_2(k)\) 只产生奇数

开放寻址的分析

均匀哈希假设:每个键以等概率产生 \(m!\) 种排列中的任一种作为探测序列(比 SUHA 更强)

不成功查找的期望探测次数

定理:装载因子 \(\alpha = \frac{n}{m} < 1\),不成功查找(或插入)期望探测次数最多 \(\frac{1}{1 - \alpha}\)

点击查看证明

\(X\) 为探测次数,事件 \(A_i\):第 \(i\) 次探测命中已占用槽

\[\{X \geq i\} = A_1 \cap A_2 \cap \cdots \cap A_{i-1}\]

\[\Pr\{A_1 \cap \cdots \cap A_{i-1}\} = \Pr\{A_1\} \cdot \Pr\{A_2 | A_1\} \cdots\]

各项概率: - \(\Pr\{A_1\} = \frac{n}{m} = \alpha\) - \(\Pr\{A_2 | A_1\} = \frac{n-1}{m-1} \leq \alpha\) - \(\Pr\{A_j | A_1 \cap \cdots \cap A_{j-1}\} = \frac{n-j+1}{m-j+1} \leq \alpha\)

因此 \(\Pr\{X \geq i\} \leq \alpha^{i-1}\)

\[ E[X] = \sum_{i=1}^{\infty} \Pr\{X \geq i\} \leq \sum_{i=1}^{\infty} \alpha^{i-1} = \frac{1}{1 - \alpha} \]

成功查找的期望探测次数

定理\(\alpha < 1\) 时,期望探测次数最多 \(\frac{1}{\alpha} \ln \frac{1}{1 - \alpha}\)

点击查看证明

搜索第 \((i+1)\) 个插入的元素的期望探测次数 \(\leq \frac{1}{1-\frac{i}{m}} = \frac{m}{m-i}\)

对所有 \(n\) 个键取平均:

\[ \frac{1}{n} \sum_{i=0}^{n-1} \frac{m}{m-i} = \frac{1}{\alpha} \sum_{i=0}^{n-1} \frac{1}{m-i} = \frac{1}{\alpha} (H_m - H_{m-n}) \\ \leq \frac{1}{\alpha} \ln \frac{m}{m-n} = \frac{1}{\alpha} \ln \frac{1}{1-\alpha} \]

实际意义

装载因子 \(\alpha\)不成功查找 \(\frac{1}{1-\alpha}\)成功查找 \(\frac{1}{\alpha}\ln\frac{1}{1-\alpha}\)
0.521.39
0.7541.85
0.9102.56
0.95203.15

\(\alpha\) 为常数(\(< 1\))时,访问需常数时间,但接近满载时性能急剧下降


布谷鸟哈希

Pagh 和 Rodler (2001) 提出

结构:两个哈希表 \(T_1, T_2\),各大小 \(m = (1+\varepsilon)n\),两个哈希函数 \(h_1, h_2\)

查找:检查 \(T_1[h_1(x)]\)\(T_2[h_2(x)]\),最坏 2 次访问

插入算法

1
2
3
4
Insert(x, i):
1. 将 x 放入 Tᵢ[hᵢ(x)]
2. 如果原来是空的: return
3. 如果原来存储着 y: 执行 Insert(y, 3-i)

像布谷鸟一样”驱逐”现有居民,循环直到找到空位

插入失败的处理

失败原因:空间不足、路径形成循环、链太长

标准方案:重新哈希——选择新哈希函数,重新插入所有元素

改进方案 (Kirsch, Mitzenmacher, Wieder, 2008):使用 stash(小辅助存储),将插入最坏情况降低到 \(O(\log n)\)

布谷鸟图

将结构看作二部图:左顶点集 \(L\) 和右顶点集 \(R\),各大小 \(m\),每个元素 \(x\) 对应边 \((h_1(x), h_2(x))\)

\(S\) 被成功存储 \(\Leftrightarrow\) 每个连通分量最多一个环

性质

  1. 查找和删除:最坏 2 次访问(可并行化)
  2. 插入:摊还常数时间
  3. 合理的内存利用率
  4. 无动态内存分配

布隆过滤器

给定集合 \(S = \{x_1, \ldots, x_n\}\),全域 \(U\),回答查询 “\(y \in S\)?”

特点:常数时间、小空间、但有假阳性

工作原理

\(m\) 位数组 \(B\),全初始化为 0

插入:对每个 \(x_j \in S\),用 \(k\) 个哈希函数 \(H_1, \ldots, H_k\),将 \(B[H_i(x_j)]\) 设为 1

查询:检查 \(H_1(y), \ldots, H_k(y)\) 对应位是否全为 1 - 任何一位为 0:\(y\) 肯定不在 \(S\) 中(无假阴性) - 全为 1:\(y\) 可能在 \(S\) 中(可能假阳性)

假阳性率分析

\(p = (1 - \frac{1}{m})^{kn} \approx e^{-\frac{kn}{m}}\)(某一位仍为 0 的概率)

假阳性概率:

\[ f = (1 - p)^k \approx (1 - e^{-\frac{kn}{m}})^k \]

最优哈希函数个数

\(f\)\(k\) 求导并令其为 0:\(k^* = \frac{m}{n} \ln 2\)

此时 \(f^* = (\frac{1}{2})^k = (0.6185)^{\frac{m}{n}}\)

示例\(\frac{m}{n} = 8\)(每元素 8 位),\(k^* \approx 5.45\),取 \(k = 5\)\(6\),假阳性率约 \(2\%\)

应用

分布式系统中的 Web 缓存:每个缓存节点维护存储 URL 的布隆过滤器,节点间交换过滤器,查找 URL 时先检查过滤器


Count-Min Sketch

数据流中频率估计的概率数据结构

结构\(t \times k\) 二维数组 \(C\)\(t\) 个独立哈希函数,\(k = \frac{1}{\varepsilon}\)\(t = \log(\frac{1}{\delta})\)

处理 \((j, c)\)

1
2
for i ← 1 to t
do C[i][hᵢ(j)] ← C[i][hᵢ(j)] + c

查询元素 \(a\) 的频率:\(\hat{f}_a = \min_{i=1}^{t} C[i][h_i(a)]\)

分析

点击查看详细分析

设随机变量 \(X_i = C[i][h_i(a)] - f_a\)(第 \(i\) 行的误差)

定义 \(Y_{i,j} = \mathbf{1}[h_i(a) = h_i(j)]\)\(j \neq a\)

\[X_i = \sum_{j \neq a} f_j \cdot Y_{i,j}\]

由 2-通用性,\(E[Y_{i,j}] = 1/k\)

\[ E[X_i] = \sum_{j \neq a} f_j / k = \frac{\|f\|_1 - f_a}{k} \leq \frac{\|f\|_1}{k} \]

由 Markov 不等式:

\[ \Pr\{X_i \geq \varepsilon \|f\|_1\} \leq \frac{E[X_i]}{\varepsilon \|f\|_1} \leq \frac{1}{\varepsilon k} = \frac{1}{2} \]

取所有 \(t\) 行最小值:

\[ \Pr\left\{\min_i X_i \geq \varepsilon \|f\|_1\right\} \leq \left(\frac{1}{2}\right)^t = \delta \]

以至少 \(1 - \delta\) 的概率:\(f_a \leq \hat{f}_a \leq f_a + \varepsilon \|f\|_1\)

总空间 \(O(\frac{1}{\varepsilon} \log \frac{1}{\delta})\) 个计数器


附录

k-独立哈希

  • 2-通用:足以保证链接法 \(O(1+\alpha)\) 期望查找
  • 5-通用:足以保证 Cuckoo 哈希 \(O(1)\) 期望插入
  • 完全独立:最强保证,但存储本身需 \(\Omega(n)\) 空间

Tabulation Hashing 虽只是 3-独立的,但对很多应用表现与完全独立一样好(Patrascu-Thorup, 2012)

装载因子的经验法则

  • 链接法:\(\alpha \leq 1\)(Java HashMap 默认 \(0.75\) 时 rehash)
  • 线性探测:\(\alpha \leq 0.5\)\(0.75\)
  • 布谷鸟哈希:\(\alpha \leq 0.5\)

同分类文章

Comments