首先,我们看一道题:
40亿个整数,占16G空间。如果我们使用set这样的容器,但是内存放不下。如果使用外排序+二分查找,在二分查找不能支持快速的随机访问。这里的解决办法就是使用位图。
数据是否在给定的整形数据中,结果是在或者不在,刚好是两种状态,那么可以使用一个二进制比特位来代表数据是否存在的信息,如果二进制比特位为1,代表存在,为0代表不存在。
所谓位图,就是用每一位来存放某种状态,适用于海量数据,数据无重复的场景。通常是用来判断某个数据存不存在的。
我们不能控制比特位,我们只能控制字节:
用char来代表0~7的bit位。
比如10这个数映射,除8就是1,8个比特位,少2个。所以,我们+1多开8个bit位。
假设我们想把18这个数映射到相应的bit位。那么我们可以先除8,这样我们就知道18在第几个char中。然后再模8,我们就知道在第几个位置上。现在我们需要在第二个位置上把0设置成1,我们怎么设置呢?
我们可以让1向左移2位,然后按位或,就可以了。
那么前面找位置都是一样的,我们怎么删除呢?
我们可以先1左移两位,再按位取反。这样我们就可以按位与了。
怎么查找这个位置上的是0还是1呢?
先将1左移两位,然后再按位与,这样如果这个位置原来是0,结果就是0。如果是1,结果就为真。
那么我们怎么开大量数据呢?
bitset<INT_MAX> bigBS;
bitset<0xFFFFFFFF> bigBS;
bitset<-1> bigBS;
看下面的题目:
给定100亿个整数,设计算法找到只出现一次的整数?
这道题有三种状态,出现0次,出现1次,出现2次及以上。但是一个bit位只能表示2种状态。所以我们需要开辟2个位图来表示:00代表0次,01代表1次,10代表2次及以上。那么我们就可以这样:
这样就可以用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;
};
我们要知道,位图只能针对整型,如果是其它类型就不起作用了。这时候就需要布隆过滤器。
我们将字符串转成一个整型再去映射,但是这样不同的字符串可能转成同一个整型,这样就会造成哈希冲突。而且这里我们不能解决哈希冲突,如果原来位置上已经存在了,就可能造成误判。 布隆过滤器就是让一个key映射多个bit位上,以减少冲突的概率,但是冲突还是无法避免。布隆过滤器特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在。
这里M的意思是开多少个bit位,K代表的是什么类型。三个哈希函数是让你映射三个值。哈希函数越多,映射的值就越多,造成的冲突的概率就会减少,但bit位也要开的越多。空间就会浪费的多。
%M就是为了能够映射到bit位的范围内。三个哈希函数都进行不同值的映射。
这三个映射哈希函数是别人经过测试的,大家可以看看这篇文章:字符串哈希算法
布隆过滤器的思想是将一个元素用多个哈希函数映射到一个位图中,因此被映射到的位置的比特位一定为1。所以可以按照以下方式进行查找:分别计算每个哈希值对应的比特位置存储的是否为零,只要有一个为零,代表该元素一定不在哈希表中,否则可能在哈希表中。
代码实现:
注意:布隆过滤器如果说某个元素不存在时,该元素一定不存在,如果该元素存在时,该元素可能存在,因为有些哈希函数存在一定的误判。
布隆过滤器不能直接支持删除工作,因为在删除一个元素时,可能会影响其它元素。
一种支持删除的方法:将布隆过滤器中的每个比特位扩展成一个小的计数器,插入元素时给k个计数器(k个哈希函数计算出的哈希地址)加一,删除元素时,给k个计数器减一,通过多占用几倍存储空间的代价来增加删除操作。
缺陷:
1. 无法确认元素是否真正在布隆过滤器中
2. 存在计数回绕
1. 增加和查询元素的时间复杂度为:O(K), (K为哈希函数的个数,一般比较小),与数据量大小无关。
2. 哈希函数相互之间没有关系,方便硬件并行运算。
3. 布隆过滤器不需要存储元素本身,在某些对保密要求比较严格的场合有很大优势。
4. 在能够承受一定的误判时,布隆过滤器比其他数据结构有这很大的空间优势。
5. 数据量很大时,布隆过滤器可以表示全集,其他数据结构不能。
6. 使用同一组散列函数的布隆过滤器可以进行交、并、差运算。
1. 有误判率,即存在假阳性,即不能准确判断元素是否在集合中(补救方法:再建立一个白名单,存储可能会误判的数据)
2. 不能获取元素本身
3. 一般情况下不能从布隆过滤器中删除元素
4. 如果采用计数方式删除,可能会存在计数回绕问题
1. 给一个超过100G大小的log file, log中存着IP地址, 设计算法找到出现次数最多的IP地址?
解决思路:
第一步:从这个100G的文件中依次读取ip,通过一个哈希函数%100算出下标i,然后将这个ip放到下标为i的小文件中。
这样切分成100个小文件,并且相同的ip,一定会进同一个小文件中。
第二步:定义一个map
第三步:如果要找出现次数最多的ip,我们可以定义一个pair
但是这里还存在一个问题:某个相同的IP太多或者映射冲突到这个编号文件的IP太多,导致某个小文件太大。
解决办法:
这里采用的是捕获异常的方式,某个相同的IP太多这个问题不需要太担心,因为相同的IP只会增加次数,不会插入内存中。如果是映射冲突到这个编号文件的IP太多,可能导致内存不足,那么我们就换个哈希函数重新切分,然后再统计。
2. 给两个文件,分别有100亿个query(网络请求或者SQL),我们只有1G内存,如何找到两个文件交集?分别给出精确算法和近似算法。
假设每个query平均30byte,100亿query就是300G。这里还是使用哈希切割,将两个文件切分成小文件。
这里切分成1000份文件,这样相同的query就会到相同下标编号的文件中。
在相同下标编号的文件中去找交集就可以了。这是精确算法,近似算法就是使用布隆过滤器。