来源:TechFlow 作者:梁唐
labuladong 前言:布隆过滤器是一种非常简单且漂亮的算法,主要用于海量数据的情况下判断某个数据是否已经存在,存在一定的误判率。
今天的文章和大家一起来学习大数据领域一个经常用到的算法——布隆过滤器。
如果看过《数学之美》的同学对它应该并不陌生,它经常用在集合的判断上,在海量数据的场景当中用来快速地判断某个元素在不在一个庞大的集合当中。
它的原理不难,但是设计非常巧妙,老实讲在看《数学之美》之前,我也没有听说过这个数据结构,所以这篇文章也是我自己学习的笔记。
原理
在我之前的理解当中,如果想要判断某个元素在不在集合当中,经典的结构应该是平衡树和hash table。 但是无论是哪一种方法,都逃不开一点,都需要存储原值。 比如在爬虫场景当中,我们需要记录下之前爬过的网站。 我们要将之前的网址全部都存储在容器里,然后在遇到新网站的时候去判断是否已经爬过了。 在这个问题当中,我们并不关心之前爬过的网站有哪些,我们只关心现在的网站有没有在之前出现过。 也就是说之前出现过什么不重要,现在的有没有出现过才重要。 我们利用平衡树或者是Trie或者是AC自动机等数据结构和算法可以实现高效的查找,但是都离不开存储下所有的字符串。 想象一下,一个网址大概上百个字符,大约0.1KB,如果是一亿个网址,就需要10GB了,如果是一百亿一千亿呢? 显然这么大的规模就很麻烦了,今天要介绍的布隆过滤器就可以解决这个问题,而且不需要存储下原值,这是一个非常巧妙的做法,让我们一起来看下它的原理。 布隆过滤器本身的结构非常简单,就是一个一维的 bool 型的数组,刚开始的时候数组内全部都是 0,这个数组的长度是 m。 对于每个新增的项,我们使用K种不同的hash算法对它计算hash值, 可以得到 K 个 hash 值,我们再用这些 hash 值对 m 取模映射到数组范围内,假设是 x,最后我们把所有 x 对应的位置标记为 1。 举个例子,假设我们一开始m是10,K是3。 我们遇到第一个插入的数据是”线性代数“,我们对它hash之后得到1,3,5,那么我们将对应的位置标记成1. 然后我们又遇到了一个数据是”高等数学“,hash之后得到1,8,9,我们还是将对应位置赋值成1,会发现1这个位置对应的值已经是1了,我们忽略就好。 如果这个时候我们想要判断”概率统计”有没有出现过,怎么办? 很简单,我们对“概率统计”再计算hash值。 假设得到1,4,5,我们去遍历一下对应的位置,发现4这个位置是0,说明之前没有添加过“概率统计”,显然“概率统计”没有出现过。 但是如果“概率统计”hash之后的结果是1,3,8呢? 我们判断它出现过就错了,答案很简单,因为虽然1,3,8这个hash组合之前没有出现过,但是对应的位置都在其他元素中出现过了,这样就出现误差了。 所以我们可以知道,布隆过滤器对于不存在的判断一定是准确的,但是对于存在的判断是有可能有错误的。代码
布隆过滤器的原理很简单,明白了之后,我们很容易写出代码:# 插入元素def BloomFilter(filter, value, hash_functions): m = len(filter)for func in hash_functions: idx = func(value) % m filter[idx] = Truereturn filter# 判断元素def MemberInFilter(filter, value, hash_functions): m = len(filter)for func in hash_functions: idx = func(value) % mif not filter[idx]:return Falsereturn True