位图和布隆过滤器

位图和布隆过滤器_第1张图片

文章目录

  • 1. 位图
    • 1.1 位图概念
  • 2. 位图的实现
    • 2.1 构造函数
    • 2.2 映射到相应的位置
    • 2.3 删除映射的位置
    • 2.4 查找在不在
  • 3. 位图应用
  • 4. 布隆过滤器
    • 4.1 布隆过滤器概念
    • 4.2 布隆过滤器的实现
      • 4.2.1 布隆过滤器的结构
      • 4.2.2 布隆过滤器的映射
      • 4.2.3 布隆过滤器的查找
      • 4.2.4 布隆过滤器删除
      • 4.2.5 布隆过滤器优点
      • 4.2.6 布隆过滤器缺陷
  • 5. 哈希切割

1. 位图

首先,我们看一道题:
在这里插入图片描述
40亿个整数,占16G空间。如果我们使用set这样的容器,但是内存放不下。如果使用外排序+二分查找,在二分查找不能支持快速的随机访问。这里的解决办法就是使用位图。
数据是否在给定的整形数据中,结果是在或者不在,刚好是两种状态,那么可以使用一个二进制比特位来代表数据是否存在的信息,如果二进制比特位为1,代表存在,为0代表不存在。

1.1 位图概念

所谓位图,就是用每一位来存放某种状态,适用于海量数据,数据无重复的场景。通常是用来判断某个数据存不存在的。

2. 位图的实现

我们不能控制比特位,我们只能控制字节:
位图和布隆过滤器_第2张图片
用char来代表0~7的bit位。

2.1 构造函数

位图和布隆过滤器_第3张图片
比如10这个数映射,除8就是1,8个比特位,少2个。所以,我们+1多开8个bit位。

2.2 映射到相应的位置

假设我们想把18这个数映射到相应的bit位。那么我们可以先除8,这样我们就知道18在第几个char中。然后再模8,我们就知道在第几个位置上。现在我们需要在第二个位置上把0设置成1,我们怎么设置呢?
位图和布隆过滤器_第4张图片
我们可以让1向左移2位,然后按位或,就可以了。
位图和布隆过滤器_第5张图片

代码实现:
位图和布隆过滤器_第6张图片

2.3 删除映射的位置

那么前面找位置都是一样的,我们怎么删除呢?
位图和布隆过滤器_第7张图片
我们可以先1左移两位,再按位取反。这样我们就可以按位与了。

代码实现:
位图和布隆过滤器_第8张图片

2.4 查找在不在

怎么查找这个位置上的是0还是1呢?
位图和布隆过滤器_第9张图片
先将1左移两位,然后再按位与,这样如果这个位置原来是0,结果就是0。如果是1,结果就为真。

代码实现:
位图和布隆过滤器_第10张图片

3. 位图应用

那么我们怎么开大量数据呢

bitset<INT_MAX> bigBS;
bitset<0xFFFFFFFF> bigBS;
bitset<-1> bigBS;

看下面的题目:
给定100亿个整数,设计算法找到只出现一次的整数

这道题有三种状态,出现0次,出现1次,出现2次及以上。但是一个bit位只能表示2种状态。所以我们需要开辟2个位图来表示:00代表0次,01代表1次,10代表2次及以上。那么我们就可以这样:
位图和布隆过滤器_第11张图片
这样就可以用2个bit位来表示了。

代码实现如下:

template<size_t N>
	class two_bitset
	{
	public:
		void set(size_t x)
		{
			int in1 = _bs1.test(x);
			int in2 = _bs2.test(x);
			if (in1 == 0 && in2 == 0)
			{
				_bs2.set(x);
			}
			else if (in1 == 0 && in2 == 1)
			{
				_bs1.set(x);
				_bs2.reset(x);
			}
			//2次以上不需要处理
		}

		bool is_once(size_t x)
		{
			return _bs1.test(x) == 0 && _bs2.test(x) == 1;
		}

	private:
		bitset<N> _bs1;
		bitset<N> _bs2;
	};

4. 布隆过滤器

我们要知道,位图只能针对整型,如果是其它类型就不起作用了。这时候就需要布隆过滤器。

4.1 布隆过滤器概念

我们将字符串转成一个整型再去映射,但是这样不同的字符串可能转成同一个整型,这样就会造成哈希冲突。而且这里我们不能解决哈希冲突,如果原来位置上已经存在了,就可能造成误判。 布隆过滤器就是让一个key映射多个bit位上,以减少冲突的概率,但是冲突还是无法避免。布隆过滤器特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在

4.2 布隆过滤器的实现

4.2.1 布隆过滤器的结构

位图和布隆过滤器_第12张图片
这里M的意思是开多少个bit位,K代表的是什么类型。三个哈希函数是让你映射三个值。哈希函数越多,映射的值就越多,造成的冲突的概率就会减少,但bit位也要开的越多。空间就会浪费的多。

4.2.2 布隆过滤器的映射

位图和布隆过滤器_第13张图片
%M就是为了能够映射到bit位的范围内。三个哈希函数都进行不同值的映射。
位图和布隆过滤器_第14张图片
这三个映射哈希函数是别人经过测试的,大家可以看看这篇文章:字符串哈希算法

然后我们可以给个默认参数:
位图和布隆过滤器_第15张图片

4.2.3 布隆过滤器的查找

布隆过滤器的思想是将一个元素用多个哈希函数映射到一个位图中,因此被映射到的位置的比特位一定为1。所以可以按照以下方式进行查找:分别计算每个哈希值对应的比特位置存储的是否为零,只要有一个为零,代表该元素一定不在哈希表中,否则可能在哈希表中。

代码实现:
位图和布隆过滤器_第16张图片
注意:布隆过滤器如果说某个元素不存在时,该元素一定不存在,如果该元素存在时,该元素可能存在,因为有些哈希函数存在一定的误判。

4.2.4 布隆过滤器删除

布隆过滤器不能直接支持删除工作,因为在删除一个元素时,可能会影响其它元素。

一种支持删除的方法:将布隆过滤器中的每个比特位扩展成一个小的计数器,插入元素时给k个计数器(k个哈希函数计算出的哈希地址)加一,删除元素时,给k个计数器减一,通过多占用几倍存储空间的代价来增加删除操作

缺陷:
1. 无法确认元素是否真正在布隆过滤器中
2. 存在计数回绕

4.2.5 布隆过滤器优点

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

4.2.6 布隆过滤器缺陷

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

5. 哈希切割

1. 给一个超过100G大小的log file, log中存着IP地址, 设计算法找到出现次数最多的IP地址

解决思路:
第一步:从这个100G的文件中依次读取ip,通过一个哈希函数%100算出下标i,然后将这个ip放到下标为i的小文件中
位图和布隆过滤器_第17张图片
这样切分成100个小文件,并且相同的ip,一定会进同一个小文件中

第二步:定义一个map countmap,依次对每个小文件统计(统计完一个文件,clear掉,再去统计另外一个文件)

第三步:如果要找出现次数最多的ip,我们可以定义一个pair maxip,每次统计完一个文件后,和maxip进行比较,如果有比maxip多的就替换。如果我们想找出现次数topk的ip,我们可以定义一个优先级队列,priority_queue,> minHeap。

但是这里还存在一个问题:某个相同的IP太多或者映射冲突到这个编号文件的IP太多,导致某个小文件太大
解决办法:
位图和布隆过滤器_第18张图片
这里采用的是捕获异常的方式,某个相同的IP太多这个问题不需要太担心,因为相同的IP只会增加次数,不会插入内存中。如果是映射冲突到这个编号文件的IP太多,可能导致内存不足,那么我们就换个哈希函数重新切分,然后再统计。

2. 给两个文件,分别有100亿个query(网络请求或者SQL),我们只有1G内存,如何找到两个文件交集?分别给出精确算法和近似算法
假设每个query平均30byte,100亿query就是300G。这里还是使用哈希切割,将两个文件切分成小文件。
位图和布隆过滤器_第19张图片
这里切分成1000份文件,这样相同的query就会到相同下标编号的文件中。
位图和布隆过滤器_第20张图片
在相同下标编号的文件中去找交集就可以了。这是精确算法,近似算法就是使用布隆过滤器。

你可能感兴趣的:(C++,c++,算法,开发语言)