目录

  • 位图
    • 位图概念
    • 位图的应用
    • 位图的实现思路
      • set
      • reset
      • test
      • 完整代码
  • 布隆过滤器
    • 布隆过滤器概念
    • 布隆过滤器的优缺点
      • 优点
      • 缺点
    • 布隆过滤器的实现思路
      • 哈希冲突的问题
      • 如何选择哈希函数个数和布隆过滤器长度
      • 插入
      • 查找
      • 删除
      • 完整代码

位图

位图概念


位图其实就是哈希的变形,他同样通过映射来处理数据,只不过位图本身并不存储数据,而是存储标记。通过一个比特位来标记这个数据是否存在,1代表存在,0代表不存在。

位图通常情况下用在数据量庞大,且数据不重复的情景下判断某个数据是否存在。

例如下面这道十分经典的题目

给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在这40亿个数
中。

关于这道题目,解法其实有很多。
1.快速排序后二分搜索。(内存可能不够,要16G内存)
2.位图处理,(40亿无符号整数用位图标记只需要512M的内存)

位图的解法差不多是这道题的最优解,只需要将所有数据读入后将对应位置置1,然后再查找那个数据所储的位置是否为1即可。


位图的应用

  1. 快速查找某个数据是否在一个集合中
  2. 排序
  3. 求两个集合的交集、并集等
  4. 操作系统中磁盘块标记

位图的实现思路

为了方便实现,位图的底层可以使用一个vector而开空间并不根据数据的个数来开,而是根据数据的范围来开(如果开的空间不够,可能有位置无法映射到)并且一个整型具有32个字节,所以如果我们要存N个数据,就只需要开N / 32 + 1的空间即可(+1是为了防止数据小于32和向上取整)。

当要操作一个数据时,先将其除以32来判断它应该处于数组中哪一个整型中。再对其%32,来判断它位于这个整型中的哪一个位上,此时再进行对应的位运算即可。


set

set即将对应标识位置1
可以通过将1左移pos个位置,再让对应位置与这个数据相即可实现。

//数据的对应标识位置1
void set(size_t x)
{//计算出在数组中哪一个整型中size_t index = x >> 5;//计算出在该整型的哪一个位上size_t pos = x % 32;//对应位置 置1_bits[index] |= (1 << pos);++_size;
}

reset

reset即将对应标识位置0

首先让1左移pos个位置,再对这个数据进行取反。然后让对应位置数据与这个数据相即可。

//数据的对应标识位置0
void reset(size_t x)
{size_t index = x >> 5;size_t pos = x % 32;//对应位置数据置零_bits[index] &= ~(1 << pos);++_size;
}

test即判断这个数据在不在,只需要让1左移pos个位置,再用对应位置进行与运算,如果为1则说明存在,0则说明不存在

test

bool test(size_t x) const
{size_t index = x >> 5;size_t pos = x % 32;return _bits[index] & (1 << pos);
}

完整代码

#pragma once
#include<vector>namespace lee
{class bitset{public://每一个位标识一个数据,一个整型4个字节,可存储32个位, 所以需要/32(或者右移五位)。+1是为了取整bitset(size_t size = 32) : _bits((size >> 5) + 1, 0), _size(0){}//数据的对应标识位置1void set(size_t x){//计算出在数组中哪一个整型中size_t index = x >> 5;//计算出在该整型的哪一个位上size_t pos = x % 32;//对应位置 置1_bits[index] |= (1 << pos);++_size;}//数据的对应标识位置0void reset(size_t x){size_t index = x >> 5;size_t pos = x % 32;//对应位置数据置零_bits[index] &= ~(1 << pos);++_size;}//判断数据是否存在bool test(size_t x) const{size_t index = x >> 5;size_t pos = x % 32;return _bits[index] & (1 << pos);}size_t size() const{return _size;}private:std::vector<int> _bits;size_t _size;};
};

布隆过滤器

我们在使用新闻客户端看新闻时,它会给我们不停地推荐新的内容,它每次推荐时要去重,去掉那些已经看
过的内容。问题来了,新闻客户端推荐系统如何实现推送去重的? 用服务器记录了用户看过的所有历史记
录,当推荐系统推荐新闻时会从每个用户的历史记录里进行筛选,过滤掉那些已经存在的记录。 如何快速查
找呢?

  1. 用哈希表存储用户记录,缺点:浪费空间
  2. 用位图存储用户记录,缺点:不能处理哈希冲突
  3. 将哈希与位图结合,即布隆过滤器

布隆过滤器概念

布隆过滤器是由布隆(Burton Howard Bloom)在1970年提出的 一种紧凑型的、比较巧妙的概率型数据结 构,特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”,它是用多个哈希函
数,将一个数据映射到位图结构中。此种方式不仅可以提升查询效率,也可以节省大量的内存空间。


布隆过滤器的优缺点

优点

  1. 增加和查询元素的时间复杂度为:O(K), (K为哈希函数的个数,一般比较小),与数据量大小无关
  2. 哈希函数相互之间没有关系,方便硬件并行运算
  3. 布隆过滤器不需要存储元素本身,在某些对保密要求比较严格的场合有很大优势
  4. 在能够承受一定的误判时,布隆过滤器比其他数据结构有这很大的空间优势
  5. 数据量很大时,布隆过滤器可以表示全集,其他数据结构不能
  6. 使用同一组散列函数的布隆过滤器可以进行交、并、差运算

缺点

  1. 有误判率,即存在假阳性(False Position),即不能准确判断元素是否在集合中(补救方法:再建立一个白 名单,存储可能会误判的数据)
  2. 不能获取元素本身
  3. 一般情况下不能从布隆过滤器中删除元素
  4. 如果采用计数方式删除,可能会存在计数回绕问题

布隆过滤器的实现思路

这里底层使用的数据结构是前面实现的位图,所以对应操作可以直接到上面看。

哈希冲突的问题

之前在哈希那一章说过,当字符串使用哈希时,无可避免的会出现哈希冲突的问题,而位图又是一个不能解决哈希冲突的数据结构,所以这就导致了一个问题,对于一个数据不能只有一个位置来标记,需要用到多个位置。于是我们需要用到多个哈希函数,来将数据映射到多个位置上面,才能确保数据的准确性。

例如下面的baidu,分别通过三种哈希函数映射到了1,4,7。将这三个位置全部置1


这里我使用了三个字符串哈希函数,分别是BKDR,SDBM,RS。

struct _BKDRHash
{//BKDRHashsize_t operator()(const std::string& key){size_t hash = 0;for (size_t i = 0; i < key.size(); i++){hash *= 131;hash += key[i];}return hash;}
};struct _SDBMHash
{//SDBMHashsize_t operator()(const std::string& key){size_t hash = 0;for (size_t i = 0; i < key.size(); i++){hash *= 65599;hash += key[i];}return hash;}
};struct _RSHash
{//RSHashsize_t operator()(const std::string& key){size_t hash = 0;size_t magic = 63689;for (size_t i = 0; i < key.size(); i++){hash *= magic;hash += key[i];magic *= 378551;}return hash;}
};

如何选择哈希函数个数和布隆过滤器长度

而如果一个数据要映射多个位置,如果布隆过滤器较小,则会导致数据马上全部映射满,此时无论进行什么操作,都会存在大量的误报率。也就是说,布隆过滤器的长度与误报率成反比,与空间利用率成反比。
并且哈希函数的个数也值得思考,哈希函数越多,映射的位置也就越多,此时准确性也就越高,但随之带来的问题就是效率的降低。也就是说,哈希函数的个数与效率成反比,准确率成正比


这张图则是各种长度以及哈希函数的效率对比图。

那么该如何选择哈希函数的个数以及布隆过滤器的长度呢?
这里引用一位大佬计算出的公式,具体求解链接我放在了文章末尾

k 为哈希函数个数,m 为布隆过滤器长度,n 为插入的元素个数,p 为误报率。

所以根据公式,我这里使用的哈希函数为3个,空间就应该开插入元素个数的五倍。


插入

数据分别映射到三个位置上,将三个位置全部置1

void set(const K& key)
{//为了减少错误率,用多个哈希函数将同一个数据映射到多个位置size_t pos1 = Hash1()(key) % _capacity;size_t pos2 = Hash2()(key) % _capacity;size_t pos3 = Hash3()(key) % _capacity;_bs.set(pos1);_bs.set(pos2);_bs.set(pos3);++_size;
}

查找

布隆过滤器的查找即分别查找映射位,一旦有任何一个为0,则说明数据不存在。如果全部为1,此时说明数据可能存在,因为可能存在将别人映射的位置误判进来,所以布隆过滤器的查找是不够准确的。所以可以这么说,布隆过滤器只提供模糊查询,如果需要精确查询,只能使用别的方法。

布隆过滤器如果说某个元素不存在时,该元素一定不存在,如果该元素存在时,该元素可能存在,因
为有些哈希函数存在一定的误判。

bool test(const K& key)
{size_t pos1 = Hash1()(key) % _capacity;size_t pos2 = Hash2()(key) % _capacity;size_t pos3 = Hash3()(key) % _capacity;if (!_bs.test(pos1) || !_bs.test(pos2) || !_bs.test(pos3)){return false;}return true;
}

删除

布隆过滤器是不支持删除操作的,因为一旦进行删除,很可能就会将别人映射的位置也置为0,导致出现错误。

但是如果非要删除的话,也不是不行。
可以将每一个比特位拓展为一个计数器,每当有数据插入时对应位置的计数器+1,数据删除是对应位的计数器-1。一个位肯定无法完成计数,需要用到多个位,此时就会导致存储空间的大量增加,使得效率下降,而本身选择布隆过滤器也是为了节省空间,这样就本末倒置了。


完整代码

#pragma once
#include"bitset.hpp"
#include<string>
namespace lee
{struct _BKDRHash{//BKDRHashsize_t operator()(const std::string& key){      size_t hash = 0;for (size_t i = 0; i < key.size(); i++){hash *= 131;hash += key[i];}return hash;}};struct _SDBMHash{//SDBMHashsize_t operator()(const std::string& key){size_t hash = 0;for (size_t i = 0; i < key.size(); i++){hash *= 65599;hash += key[i];}return hash;}};struct _RSHash{//RSHashsize_t operator()(const std::string& key){size_t hash = 0;size_t magic = 63689;for (size_t i = 0; i < key.size(); i++){hash *= magic;hash += key[i];magic *= 378551;}return hash;}};template<class K = std::string, class Hash1 = _BKDRHash, class Hash2 = _SDBMHash, class Hash3 = _RSHash>class BloomFilter{public:BloomFilter(size_t num): _bs(num), _capacity(num), _size(0){}void set(const K& key){//为了减少错误率,用多个哈希函数将同一个数据映射到多个位置size_t pos1 = Hash1()(key) % _capacity;size_t pos2 = Hash2()(key) % _capacity;size_t pos3 = Hash3()(key) % _capacity;_bs.set(pos1);_bs.set(pos2);_bs.set(pos3);++_size;}bool test(const K& key){size_t pos1 = Hash1()(key) % _capacity;size_t pos2 = Hash2()(key) % _capacity;size_t pos3 = Hash3()(key) % _capacity;if (!_bs.test(pos1) || !_bs.test(pos2) || !_bs.test(pos3)){return false;}return true;}size_t size() const{return _size;}private:lee::bitset _bs;size_t _size;size_t _capacity;};
};

参考文章:

  • 详解布隆过滤器的原理、使用场景和注意事项

海量数据处理(一) :位图与布隆过滤器的概念以及实现相关推荐

  1. 常见数据结构和算法实现(排序/查找/数组/链表/栈/队列/树/递归/海量数据处理/图/位图/Java版数据结构)

    常见数据结构和算法实现(排序/查找/数组/链表/栈/队列/树/递归/海量数据处理/图/位图/Java版数据结构) 数据结构和算法作为程序员的基本功,一定得稳扎稳打的学习,我们常见的框架底层就是各类数据 ...

  2. 哈希思想的应用 - 位图,布隆过滤器

    目录 位图 海量数据处理问题: 哈希思想的应用:位图 位图实现: 位图处理海量数据 布隆过滤器 引: 布隆过滤器的概念: 布隆过滤器简单实现: 布隆过滤器的查找 布隆过滤器的删除 布隆过滤器优点 布隆 ...

  3. 哈希表、位图、布隆过滤器

    目录 哈希的概念 哈希函数 哈希冲突和解决方法 闭散列 插入 查找 删除 开散列 插入 查找 删除 哈希表(开散列)整体代码 位图 位图模拟实现思路分析: 位图应用 布隆过滤器 本文介绍unorder ...

  4. 冰冰学习笔记:位图与布隆过滤器

    欢迎各位大佬光临本文章!!! 还请各位大佬提出宝贵的意见,如发现文章错误请联系冰冰,冰冰一定会虚心接受,及时改正. 本系列文章为冰冰学习编程的学习笔记,如果对您也有帮助,还请各位大佬.帅哥.美女点点支 ...

  5. 数据结构-哈希-位图与布隆过滤器

    位图与布隆过滤器 一,位图 题目分析 位图设计 位图代码 经典题目 二,布隆过滤器 布隆过滤器概念 布隆过滤器的插入 布隆过滤器的结构 布隆过滤器总结 经典题目 三,哈希切割 一,位图 题目分析

  6. 海量数据处理(位图和布隆过滤器)

    哈希切割 给一个超过100G大小的log file, log中存着IP地址, 设计算法找到出现次数最多的IP地址? 与上题条件相同,如何找到top K的IP?如何直接用Linux系统命令实现 解决思路 ...

  7. Hash,位图,布隆过滤器

    文章目录 哈希概念 哈希冲突 哈希函数 除留余数法--(非常常用) 直接定址法--(常用) 平方取中法--(了解) 折叠法--(了解) 随机数法--(了解) 数学分析法--(了解 ) 哈希冲突解决 闭 ...

  8. 为什么亿级数据量时要使用位图?位图和布隆过滤器有什么关系?

    文章目录 一.为什么要使用位图 二.什么是位图 三.布隆过滤器 四.位图的常见使用场景 4.1 生成看似无规律却呈趋势递增的号段式ID 4.2 搜索引擎爬虫网页去重 4.3 大型网站每天UV数量统计 ...

  9. 哈希结构(图文详解)【哈希表,哈希桶,位图,布隆过滤器】

    哈希结构 哈希概念 常见的K-V结构,实现了元素关键码与元素值的映射关系,但没有实现元素关键值与元素存储位置的映射关系,在遍历过程中,一般的顺序表或搜索二叉树要进行关键值的多次比较,其中顺序表的时间复 ...

最新文章

  1. java语言的主要的知识点
  2. Dos批处理常用命令大全入门
  3. 商汤组了「最强大脑」局,正儿八经解释为啥搞起电竞AI
  4. 数据绑定控件之Repeater
  5. 生产服务器环境最小化安装后Centos 6.5优化配置备忘
  6. 阿里云移动测试平台MQC移动测试沙龙第3期【北京站】
  7. 框架学习之Hibernate 第十节 事务原理与分析
  8. Python中字符串操作函数string.split('str1')和string.join(ls)
  9. 使用 Spring Boot Security 进行安全控制
  10. 2017年网络犯罪现状分析报告
  11. SpringBoot配置参数绑定@ConfigurationProperties@Value
  12. Linux系统调用之open(), close() (转载)
  13. 【基础版】整数加减乘除计算器
  14. 玩转飞思卡尔在线调试工具FreeMaster
  15. android 开机动画制作
  16. 如何做接口测试呢?接口测试有哪些工具【小白都会系列】
  17. 虚拟主机需要备案吗?
  18. 当你一个人走过你们曾经走过的大街小巷,你会有何感受?
  19. 文学类的小说,言情类的,没有排行榜上的
  20. WMS系统--移库逻辑

热门文章

  1. JAVA中ListIterator和Iterator详解与辨析
  2. Spring Cloud Gateway 源码解析(1) —— 基础
  3. LinuxC-运算符
  4. 第一周 第二天的linux学习
  5. TensorFlow机器学习实战指南之第一章
  6. 熬过了互联网“寒冬”,接下来的金三银四你该怎么面试进BAT?
  7. Ubuntu 16.04 安装wine
  8. python爬虫知识点总结(十九)Scrapy命令行详解
  9. Android 自定义的开关按钮——SwitchButton
  10. 如何获取FragmentTabHost中指定标签页的Fragment