具体算法3 - 位图&布隆过滤器

本章关键词

大量数据存储、查找、判重 、布尔代数、节省内存、小概率容错

布隆过滤器不是一个算法,确切的来说,它是一个数据结构。那么为什么将它归结为算法呢?因为它提供了一个全新的处理思路,也就让我们在设计算法的时候有了一个好用的工具。

问题解析

我们想象这样一个场景:在一个网络爬虫中,你需要实现对网页的去重功能,我们需要判断网页的 URL 是否已经被爬取过,如果已经被爬取,则不必再爬取网页。

分析:这个应用的场景其实非常好处理,最简单的思路就是使用散列表进行去重。但是,如果我们需要爬取的网页非常多,比如超过 100 亿,使用散列表存储信息就会耗费巨量的存储空间。如果我们只是需要完成添加和数据去重,有没有更加高效且节省存储空间的方法呢?

答案是肯定的,那就是使用位图&布隆过滤器。

算法解析

布隆过滤器是在位图的基础上完成的,所以我们要首先了解位图。

1.位图
1.1概念

首先我们看一个简单一点的场景:我们有 1 千万个整数,整数的范围在 1 到 1 亿之间。如何快速查找某个整数是否在这 1 千万个整数中呢?

当然,这个问题可以使用散列表解决。不过,我们可以使用一种比较 “特殊” 的散列表来实现,这就是 位图

在散列表中,存储的数据可能是指针、数字、字符...而位图的特殊之处在于,它的数据元素为一个二进制为,即 0(true) 或者 1(false) 。

以上面的例子为例,我们可以申请一个大小为 1 亿,数据类型为 bool 类型的数组。我们将需要判定的一千万个整数作为数组下标,将对应的数组设置成 true。例如,在数据中出现了 5 ,我们可以设置 array[5] = true。

当我们要查询某个整数 K 是否在数据中的时候,只需要查询对应的数组 array[K] 的值,如果为 true,证明存在,反之不存在。

1.2二进制位的表示
在许多编程语言中提供的 bool 类型是 1字节 的,这样算来,并不能节省太多空间。那么,如何在编程语言中表示二进位呢?
这里就需要用到位运算了,我们可以借助计算机中的内置类型,通过位运算表示某个位置的二进制位。这里使用专栏作者给出的代码解释:

public class BitMap { // Java中char类型占2个字节,即16bit
  private char[] bytes; // 我们使用char类型作为存储二进制的基本类型
  private int nbits;
  
  public BitMap(int nbits) {
    this.nbits = nbits; // 一共有 nbits 个二进制位需要保存
    this.bytes = new char[nbits/16+1]; //  一个 char 可以保存 16 个 bit ,所以需要 nbit/16+1 个 char
  }

  public void set(int k) {
    if (k > nbits) return;
    int byteIndex = k / 16; // 计算 k 会落到哪个 char 上,即在 char[ k / 16 ] 中包含了 k
    int bitIndex = k % 16; // 计算 k 会落到 char 到哪一位上,即 char[k / 16] 中的第 k % 16 -1 个 bit (-1是因为数组从 0 开始计数)
    bytes[byteIndex] |= (1 << bitIndex);
  }

  public boolean get(int k) {
    if (k > nbits) return false;
    int byteIndex = k / 16; // 同上
    int bitIndex = k % 16; // 同上
    return (bytes[byteIndex] & (1 << bitIndex)) != 0;
  }
}

1.3位图的问题
在前面的例子中,数据的范围在 0~ 1亿,所以我们开辟了 1亿 个bit,如果条件改成我们有 1 千万个整数,整数的范围在 1 到 100 亿之间。这时候,我们该怎么办呢?如果你开辟 100 亿个 bit,耗费的空间反而比散列表还要大。

针对这个问题,我们建立了布隆过滤器

2.布隆过滤器
2.1概念

布隆过滤器的思想,是在位图中添加多个哈希函数,减小需要开辟的存储空间

借用之前的例子,我们设计一个 f(x) = x%n 的哈希函数(x代表数字,n代表你想要开辟的存储空间,这里我们设置 n=1亿),就可以将需要开辟的空间大小从 100亿 缩小为 1亿 。

当然,使用哈希函数一大问题是会存在散列冲突,针对这种情况,我们可以使用多个哈希函数共同确定一个一个数是否存在:

我们使用 K 个哈希函数,对同一个数字进行求哈希值,那会得到 K 个不同的哈希值,我们分别记作 X1​,X2​,X3​,…,XK​。我们把这 K 个数字作为位图中的下标,将对应的 BitMap[X1​],BitMap[X2​],BitMap[X3​],…,BitMap[XK​]都设置成 true,也就是说,我们用 K 个二进制位,来表示一个数字的存在。
当我们要查询某个数字是否存在的时候,我们用同样的 K 个哈希函数,对这个数字求哈希值,分别得到 Y1​,Y2​,Y3​,…,YK​。我们看这 K 个哈希值,对应位图中的数值是否都为 true,如果都是 true,则说明,这个数字存在,如果有其中任意一个不为 true,那就说明这个数字不存在。

下面的图可能会帮助你理解:
布隆过滤器.jpg

2.2布隆过滤器的缺陷
敏感的朋友可能已经发现了,使用布隆过滤器可能会出现判断错误的情况:

布隆过滤器的误判有一个特点,那就是,它只会对存在的情况有误判。如果某个数字经过布隆过滤器判断不存在,那说明这个数字真的不存在,不会发生误判;如果某个数字经过布隆过滤器判断存在,这个时候才会有可能误判,有可能并不存在。不过,只要我们调整哈希函数的个数、位图大小跟要存储数字的个数之间的比例,那就可以将这种误判的概率降到非常低。

请看这个图:
布隆过滤器-错误.jpg

所以,布隆过滤器只能使用在可以容忍一定错误的情境下。

3.性能分析
布隆过滤器在一定条件下可以替代散列表,在空间上,布隆过滤器无疑会减少非常多的空间消耗。而在时间上,它的表现是否依然优秀呢?答案是肯定的:

布隆过滤器用多个哈希函数对同一个网页链接进行处理,CPU 只需要将网页链接从内存中读取一次,进行多次哈希计算,理论上讲这组操作是 CPU 密集型的。而在散列表的处理方式中,需要读取散列值相同(散列冲突)的多个网页链接,分别跟待判重的网页链接,进行字符串匹配。这个操作涉及很多内存数据的读取,所以是内存密集型的。我们知道 CPU 计算可能是要比内存访问更快速的,所以,理论上讲,布隆过滤器的判重方式,更加快速。

总结

这一节中,我们学习了位图和布隆过滤器,但是我最想说的,还是对二进制位的使用:

  • 一个二进制位只能表示 0 或 1 ,所以它能表达的信息有限
  • 二进制位最适合表达 “是” 或 “否”,从这个思路延伸下去,我暂时可以想到这样几个应用场景:
  1. 查询某个元素 是否 在一个集合中
  2. 根据布尔代数,二进制可以进行 与 、或 、非 三种运算。
  3. 由上一条扩展,我们可以查询 某个既在 A 集合 又在 B 集合中的元素(使用 与 运算)。等
  • 哈希函数的使用:可以减少对空间的占用
  • 如果你对数组的下标足够敏感,你可以使用下标存储一定的信息
  • 布尔过滤器一定会出现误判
  • 如果想了解更多内容,你可以看这里

以上就是本节的所有内容希望对你有所收获

注:本文章的主要内容来自我对极客时间app的《数据结构与算法之美》专栏的总结,我使用了大量的原文、代码和截图,如果想要了解具体内容,可以前往极客时间

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,924评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,781评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,813评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,264评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,273评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,383评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,800评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,482评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,673评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,497评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,545评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,240评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,802评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,866评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,101评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,673评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,245评论 2 341

推荐阅读更多精彩内容

  • 引自:https://www.cnblogs.com/liyulong1982/p/6013002.html 布隆...
    青玉_f18c阅读 984评论 0 4
  • 在日常生活中,包括在设计计算机软件时,我们经常要判断一个元素是否在一个集合中。比如在字处理软件中,需要检查一个英语...
    jackcooper阅读 816评论 0 4
  • 一、引言 相信大家对网页爬虫一定不陌生,爬虫的工作原理是: 爬理:通过解析已经爬取页面中的网页链接,然后再爬取这些...
    StevenHD阅读 1,117评论 0 0
  • 只祝你之后的生活不只有食色性也 更有风花雪月 管他个痴情玫瑰 刺痛了尽管背叛 你那奢侈的感情 毫不吝啬的挥霍 ...
    叶世贤阅读 166评论 0 0
  • 张凯是我们班出了名的的疯子,天天下课不是干这个奇葩的事,就是看干那个奇葩的事,这不他又惹事了。 那天下课我们在...
    龙族阅读 168评论 1 1