Cuckoo Hash
最近我在看CMU的15/445课程,在hash table部分提到了Cuckoo Hash,感觉还是挺有意思的,关于其性质的证明比较复杂,我没看太懂。
背景
在介绍Cuckoo hash之前,我们先来看看Chained Hash table。
在这种模式下,冲突的元素会被放到同一个bucket,一个bucket内的元素被组织成链表的形式,一般会设置负载因子
α
=
n
m
\alpha=\frac{n}{m}
α=mn,n是元素的数量,m是bucket的数量。当
α
\alpha
α超过设定的阈值后,会将m翻倍以减少后续插入时可能的冲突。
- Insertion 这种方式的insertion代价为 O ( 1 ) O(1) O(1)
- Lookup 这种方式的Lookup代价最坏为 O ( n ) O(n) O(n),最坏代价的期望值为 log n log log n \frac{\log n}{\log \log n} loglognlogn
Cockoo Hash
引入Cockoo Hash的目的就是希望能够同时降低插入和查询的代价。Cockoo Hash有两个table,分别对应两个hash函数 h 1 h_1 h1和 h 2 h_2 h2。元素x有两个可能的插入位置 h 1 ( x ) h_1(x) h1(x)和 h 2 ( x ) h_2(x) h2(x)
- Lookup 代价为
O
(
1
)
O(1)
O(1)
查询时只需要查看元素可能存在的两个位置即可 - Insertion 代价接近
O
(
1
)
O(1)
O(1)
插入操作最麻烦,插入元素X时,先计算其在table 1中的位置 h 1 ( X ) h_1(X) h1(X),如果该位置为空,则直接插入;如果不为空,则将原来的旧值Y移除,将X插入该位置,然后尝试将Y插入table 2,插入Y的操作与X类似,重复该操作直至元素插入成功。
插入可能会陷入循环,这会导致插入失败。这种情况下,需要执行rehash操作,即选取新的hash函数 h 1 h_1 h1和 h 2 h_2 h2,将所有元素重新插入至表中。 - Deletion 代价为
O
(
1
)
O(1)
O(1)
删除时需要查看两个位置,删除对应的元素即可
Cockoo Graph
要分析Cuckoo Hash,需要用到Cockoo Graph,Cockoo Graph是一个二分图,结构如下:
- Node 节点对应表中的slot
- Edge 边对应插入的元素,它连接了元素在table中可能插入的位置。插入X时即在graph中插入一条边,连接X可能插入的两个位置
性质
-
claim 1
元素X插入成功当且仅当包含边X的连通分支最多有一个环路
证明:假定节点数为n,连通分支中有两个及以上环路的情况下,边的数目必然大于或等于n+1,这表明待插入的元素比slot多,插入必然失败。
当连通分支中无环路时,换位操作一定会在有限步内终止;连通分支中存在环路时,换位操作也会在有限步内终止,不过情况会比无环路时稍复杂,这个可以画图来进行思考(后面有时间我再补图和简单的证明)。 -
claim 2
如果元素X被插入到包含K个节点的连通分支中, 插入过程最多有2K次换位操作
不存在环路时,结论是显然的;存在环路时,考虑是单一环路(0型)还是有环路+非环路(6字型)的情况。单一环路时,最多将所有元素轮换一遍即可插入成功;环路+非环路情况下,需要分开考虑插入元素在环路还是在非环路上的情形(有时间再画图)。
-
claim 3
m为节点数目,n为边的数目,如果 m ≥ ( 1 + ϵ ) n m\ge(1+\epsilon)n m≥(1+ϵ)n ∀ ϵ > 0 \forall \epsilon\gt0 ∀ϵ>0,插入成功且不引发rehash操作的时间复杂度为 O ( 1 + ϵ − 1 ) O(1+\epsilon^{-1}) O(1+ϵ−1)。单次rehash操作且成功的时间复杂度为 O ( m + n ϵ − 1 ) O(m+n\epsilon^{-1}) O(m+nϵ−1),由于要确保 m ≥ ( 1 + ϵ ) n m\ge(1+\epsilon)n m≥(1+ϵ)n成立,所以会随插入元素数目的变化更新table(扩充并rehash),修正以后的插入代价的期望值为 O ( 1 + ϵ + ϵ − 1 ) O(1+\epsilon+\epsilon^{-1}) O(1+ϵ+ϵ−1)
Conclusion
在满足上述前提条件下 m ≥ ( 1 + ϵ ) n m\ge(1+\epsilon)n m≥(1+ϵ)n,Cockoo Hash具有如下性质
- 查询和删除的最坏代价均为 O ( 1 ) O(1) O(1)
- 插入的代价为 O ( 1 + ϵ + ϵ − 1 ) O(1+\epsilon+\epsilon^{-1}) O(1+ϵ+ϵ−1)