bloom filter
intro
布隆过滤器是一种很有意思的数据结构,它的用途是检测某个元素是否在一个集合中。
首先,有一个数组,它的元素全部是0,然后共有m个坑:
我现在有一个集合 S = { x , y , z } S=\left\{ x,y,z \right\} S={x,y,z},对于每一个元素,通过3个hash函数,将其打到数组上,打中的位置设置为1。
比如 x x x,三次hash后,数组上就会有3个位置变为1(蓝色线条)。
至于hash函数,你可以认为它对 x x x做了处理,最后模上数组的长度得到一个下标。
注意,元素hash后可能打中同一个坑,这点不必惊奇。当m的值(也就是数组的长度)越来越大,这种情况的概率就会越来越小。
现在我来了一个 w w w,我要问: w w w在不在 S S S中?
将 w w w也三次hash一下:
- 如果有打中0的情形,那么,它肯定不在 S S S中。
- 如果打中的全部是1,那么它很有可能在 S S S中,也就是说,可以判定它在 S S S中,并带有一定的错误概率。
more general
我们的根本目的是减少错误概率。
现在考虑一般情况。
- 数组的长度为 m m m
- 集合为 S = { x 1 , x 2 , … , x n } S=\left\{ x_1,x_2,\dots,x_n \right\} S={x1,x2,…,xn},有 n n n个元素
- hash函数有 k k k个: h 1 , h 2 , … , h k h_1,h_2,\dots,h_k h1,h2,…,hk, 0 ≤ h i ( x j ) < m ( 1 ≤ i ≤ k , 1 ≤ j ≤ n ) 0 \le h_i(x_j)\lt m(1 \le i \le k, 1 \le j \le n) 0≤hi(xj)<m(1≤i≤k,1≤j≤n),换句话说,每个元素 x i x_i xi的每次hash的下标都落在数组内
- hash函数产生的下标是等概率均匀分布的,不是说全部挤在前面或者某一个地方
好,现在我们考虑一个元素(比如 x 1 x_1 x1)的插入(占坑)。
经过一次hash后,某个坑为1的概率为:
1 m \frac{1}{m} m1
某个坑为0的概率是:
1 − 1 m 1-\frac{1}{m} 1−m1
k k k个hash函数过后,某个坑依旧为0的概率是:
( 1 − 1 m ) k (1-\frac{1}{m})^k (1−m1)k
因为 lim m → + ∞ ( 1 − 1 m ) − m = e \lim\limits_{m\to+\infty}(1-\frac{1}{m})^{-m}=e m→+∞lim(1−m1)−m=e,所以
lim m → ∞ ( 1 − 1 m ) k = lim m → ∞ [ ( 1 − 1 m ) − m ] − k m = e − k m \lim\limits_{m\to\infty}(1-\frac{1}{m})^k=\lim\limits_{m\to\infty}[(1-\frac{1}{m})^{-m}]^{-\frac{k}m{}}=e^{-\frac{k}{m}} m→∞lim(1−m1)k=m→∞lim[(1−m1)−m]−mk=e−mk
我们会假设数组的长度 m m m无穷大,所以上面的式子是成立的。
完成了一个元素的插入后,现在我插入 n n n个元素。
n n n个元素插入后,某个坑依旧为0的概率是:
e − n k m e^{-\frac{nk}{m}} e−mnk
于是某个坑为1的概率是:
1 − e − n k m 1-e^{-\frac{nk}{m}} 1−e−mnk
现在我来了一个元素 y y y, y y y并不在 S S S中。
y y y经过 k k k个hash函数 后,全部打到了标记为1的坑,这个概率是:
( 1 − e − n k m ) k (1-e^{-\frac{nk}{m}})^k (1−e−mnk)k
好了,我们找到了最终的函数。
目标:使 f = ( 1 − e − n k m ) k f=(1-e^{-\frac{nk}{m}})^k f=(1−e−mnk)k
最小。
f = e ln ( 1 − e − n k m ) k = e k ln ( 1 − e − n k m ) f=e^{\ln(1-e^{-\frac{nk}{m}})^k}=e^{k\ln(1-e^{-\frac{nk}{m}})} f=eln(1−e−mnk)k=ekln(1−e−mnk)
令
g
=
k
ln
(
1
−
e
−
n
k
m
)
g=k\ln(1-e^{-\frac{nk}{m}})
g=kln(1−e−mnk)
问题转化为求 g g g的最小值。
∂ g ∂ k = ln ( 1 − e − n k m ) + ( 1 1 − e − n k m ) ( − e − n k m ) ( − n m ) ( k ) \frac{\partial g}{\partial k}=\ln(1-e^{-\frac{nk}{m}})+(\frac{1}{1-e^{-\frac{nk}{m}}})(-e^{-\frac{nk}{m}})(-\frac{n}{m})(k) ∂k∂g=ln(1−e−mnk)+(1−e−mnk1)(−e−mnk)(−mn)(k)
令 ∂ g ∂ k = 0 \frac{\partial g}{\partial k}=0 ∂k∂g=0
同时,令
e − n k m = p e^{-\frac{nk}{m}}=p e−mnk=p
于是
n m = ln p − k \frac{n}{m}=\frac{\ln p}{-k} mn=−klnp
那么
ln ( 1 − p ) + ( 1 1 − p ) ( p ) ( ln p − k ) ( k ) = ln ( 1 − p ) − p 1 − p ln p = 0 \ln(1-p)+(\frac{1}{1-p})(p)(\frac{\ln p}{-k})(k)=\ln(1-p)-\frac{p}{1-p} \ln p=0 ln(1−p)+(1−p1)(p)(−klnp)(k)=ln(1−p)−1−pplnp=0
整理一下:
( 1 − p ) ln ( 1 − p ) = p ln p (1-p)\ln (1-p)=p \ln p (1−p)ln(1−p)=plnp
得到
p = 1 2 p=\frac{1}{2} p=21
于是
k = m n ln 2 k=\frac{m}{n} \ln 2 k=nmln2
k , m , n k,m,n k,m,n满足 k = m n ln 2 k=\frac{m}{n} \ln 2 k=nmln2能够使得 f f f最小。
也就是说,如果数组长度比上元素个数为8的话( m n = 8 \frac{m}{n}=8 nm=8),那么hash函数的个数最好有 8 ln 2 ≈ 5.45 8\ln 2 \approx5.45 8ln2≈5.45个(你可以取个整)。
这是一个令人愉快的结果。