布隆过滤器 Bloom Filter
基本概念
如果想要判断一个元素是不是在一个集合里,一般想到的是将所有元素保存起来,然后通过比较确定。链表,树等等数据结构都是这种思路. 但是随着集合中元素的增加,我们需要的存储空间越来越大,检索速度也越来越慢(O(n),O(logn))。不过世界上还有一种叫作散列表(又叫哈希表,Hash table)的数据结构。它可以通过一个Hash函数将一个元素映射成一个位阵列(Bit array)中的一个点。这样一来,我们只要看看这个点是不是1就可以知道集合中有没有它了。这就是布隆过滤器的基本思想。
Hash面临的问题就是冲突。假设Hash函数是良好的,如果我们的位阵列长度为m个点,那么如果我们想将冲突率降低到例如 1%, 这个散列表就只能容纳m / 100个元素。显然这就不叫空间效率了(Space-efficient)了。解决方法也简单,就是使用多个Hash,如果它们有一个说元素不在集合中,那肯定就不在。如果它们都说在,虽然也有一定可能性它们在说谎,不过直觉上判断这种事情的概率是比较低的
优点
相比于其它的数据结构,布隆过滤器在空间和时间方面都有巨大的优势。布隆过滤器存储空间和插入/查询时间都是常数。另外, Hash函数相互之间没有关系,方便由硬件并行实现。布隆过滤器不需要存储元素本身,在某些对保密要求非常严格的场合有优势。
缺点
布隆过滤器的缺点和优点一样明显。误算率是其中之一。随着存入的元素数量增加,误算率随之增加。但是如果元素数量太少,则使用散列表足矣。
另外,一般情况下不能从布隆过滤器中删除元素。我们很容易想到把位列阵变成整数数组,每插入一个元素相应的计数器加1, 这样删除元素时将计数器减掉就可以了。然而要保证安全的删除元素并非如此简单。首先我们必须保证删除的元素的确在布隆过滤器里面. 这一点单凭这个过滤器是无法保证的。另外计数器回绕也会造成问题
误判
构建布隆过滤器时我们可以自定义两个参数,和。m和 k
如果我们有一个具有m比特和k个哈希函数的布隆过滤器,插入一次后某一位仍然为零的概率是

然后,插入n次后,仍为零的概率为

那么这就意味着误判的概率是

在这些方程的每一个中,提高k值(散列函数的数量)将使误判的概率降低。然而 k值很大并不能线性的降低误判率。在这里我们假设程序员已经确定了他们要使用的空间大小m,他们大概知道数据的规模是多大 n 。那么最小化这个方程的值是

应用
网页URL的去重,垃圾邮件的判别,集合重复元素的判别,查询加速(比如基于key-value的存储系统)等
相关理论
- 鸽巢定理
- 生日悖论
- hash表
程序示例
import hashlib
def h1(data):
h = hashlib.sha256(data)
return hash(h.digest().encode('base64'))
def h2(data):
h = hashlib.sha1(data)
return hash(h.digest().encode('base64'))
class BloomFilter(object):
def __init__(self, capacity, num_of_hash):
self.capacity = capacity
self.data = [0] * capacity
self.num = num_of_hash
self.n = 0
def insert(self, element):
if self.num == 1:
has1 = h1(element)%self.capacity
self.data[has1] = 1
elif self.num ==2:
has1 = h1(element)%self.capacity
has2 = h2(element)%self.capacity
self.data[has1] =1
self.data[has2] = 1
self.n+=1
def search(self,element):
if self.num ==1:
has1 = h1(element)%self.capacity
if self.data[has1] ==0:
return "Not in Filter"
elif self.num ==2:
has1 = h1(element)%self.capacity
has2 = h2(element)%self.capacity
if self.data[has2]==1 and self.data[has1]==0:
return "Not in Filter"
prob = (1.0 - ((1.0 - 1.0/self.capacity)**(self.num*self.n))) ** self.num
return 'Mabye be in Filter with false probability{}'.format(prob)
def main():
bloom = BloomFilter(10,2)
bloom.insert('Hello')
print(bloom.data)
print(bloom.search('Hello'))
print(bloom.search('World'))
if __name__ == '__main__':
main()
输出
[1, 0, 0, 0, 0, 0, 0, 0, 0, 1]
Mabye be in with false 0.0361
Not in Filter