gitee仓库:https://gitee.com/WangZihao64/data-structure-and-algorithm/tree/master/BloomFilter
位图概念
所谓位图,就是用每一位来存放某种状态,适用于海量数据,数据无重复的场景。通常是用来判断某个数据存不存在的。
代码实现
把某一位设置为1
-
找到x所处在的那一位: 先确定这个数应该处在第几个整数,也就是index=x/8;然后通过把x对8取模,得到x在第几号的第pos个位置
-
把改为设置为1: 把1左右j位,然后得到00…010…00,用这个数和整数进行按位或的操作
void set(size_t x)
{
int i=x/8;
int j=x%8;
_bits[i]|=(1<<j);
}
把某一位设置为0
1.找到那一位: 和上面的方法一样
2.把这位设置为0: 把1左右pos位,然后得到00…010…00,把这个数按位取反(避免改变其他操作),然后和第index个整数进行按位与的操作
void reset(size_t x)
{
int i=x/8;
int j=x%8;
_bits[i]&=~(1<<j);
}
判断某一位是否为1
1.找到那一位: 和上面的方法一样
2.把这位设置为0: 把1左右pos位,然后得到00…010…00,然后返回这个数和第index个整数进行按位与的结果
bool test(size_t x)
{
int i=x/8;
int j=x%8;
return _bits[i]&(1<<j);
}
位图的应用
- 快速查找某个数据是否在一个集合中
- 排序+去重
- 操作系统中磁盘的标记等
缺点:
只能处理整形数据
应用举例
1.给定100亿个整数,设计算法找到只出现一次的整数?
思路:使用两个位图,如果一次都没出现两个位图都是0,0但是如果出现一次就把第第二个位图变为1,即0,1,如果出现多次就把第一个变为1,第二个恢复,即1,0,然后遍历位图即可
2.给两个文件,分别有100亿个整数,我们只有1G内存,如何找到文件的交集
给自放进一个位图里,(找交集记得先去重),同时在2个位图就是交集
3.一个文件有100亿个int,1G内存,设计算法,找出出现次数不超过2次的所有整数
和题目1是一样的,0次是00,1次是01,2次是10,3次及以上是11
- 给一个超过100G大小的log file, log中存着IP地址, 设计算法找到出现次数最多的IP地址? 如何找到top K的IP?如何直接用Linux系统命令实现?
这个用位图就做不了了,我们可以使用哈希切割,先创建100个小文件A0-A99,然后计算i = hash(IP)%100,i是多少,IP就进入编号为i的文件中,也就是Ai文件中,这样相同的IP就都进入了同一个文件中。先将一个小文件加载到内存中,依次读取放入unordered_map<string, int> 中,同时用一个pair<string, int> max记录当前出现次数最多的IP,然后不断更新记录当前的max,这样就得到出现次数最多的IP地址
布隆过滤器概念
如何快速查找?
- 哈希表: 用哈希表存储用户的历史记录。缺点: 空间消耗比较大
- 位图: 用无图存储用户的历史记录。 缺点: 不能处理哈希冲突问题
- 布隆过滤器: 将哈希表和位图结合使用。
布隆过滤器实际上是一个很长的二进制向量和一系列随机映射函数。它通过多个哈希函数将一个数据映射到位图的结构中(也就是一个数据映射位图的多个位置,这样就可以减少冲突的概率)。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。
这里放置3个哈希函数,用了映射不同的概率。
k 为哈希函数个数
m 为布隆过滤器长度
n 为插入的元素个数
p 为可接受该容器的误报率(0-1)
其中k=3,整体算下来,m≈4.34n,所以这里我们选择m为5。
代码实现
插入
- 先用不同的哈希函数计算出该数据分别要映射到位图的哪几个位置
- 然后把位图中的这几个位置设置为1
void set(const K& key)
{
size_t hash1=HashFunc1()(key)%(N*5);
size_t hash2=HashFunc2()(key)%(N*5);
size_t hash3=HashFunc3()(key)%(N*5);
_bs.set(hash1);
_bs.set(hash2);
_bs.set(hash3);
}
查找
- 先用不同的哈希函数计算出该数据分别要映射到位图的哪几个位置
- 然后判断位图中的这几个位置是否都为1,如果有一个不为1,说明该元素一定不在容器中,否则表示在容器中
注意:
可能会误报,判断在是不准确的,判断不在是准确的。(因为一个数据判断出它是在的,可能是它映射的几个数据可能是其它数据映射导致这几个位置为1的,所以判断结果为在,该结果有误判;而判断一个数据为不在时,那这个数据是一定不在的,因为它映射的几个位置不全为1)
bool test(const K& key)
{
//布隆过滤器如果查到这个值出现过,不一定真正出现过,但是没出现过就一定没出现过
size_t hash1=HashFunc1()(key)%(N*5);
if(!_bs.test(hash1))
{
return false;
}
size_t hash2=HashFunc2()(key)%(N*5);
if(!_bs.test(hash2))
{
return false;
}
size_t hash3=HashFunc3()(key)%(N*5);
if(!_bs.test(hash3))
{
return false;
}
return true;
}
删除
布隆过滤器不能直接支持删除工作,因为在删除一个元素时,可能会影响其他元素。也就是说,要删除的元素映射的位置可能会是其它元素映射的位置,所以直接删除元素会给后期查找某个元素带来极大的误判。
解决:
可以考虑给把比特位(根据需求选择用多少个比特位)扩展成为一个小的计数器,插入元素时,给计数器加1,删除元素时,给计数器减1。当然这个肯定会带来更大的空间开销,牺牲了布隆过滤器的优势。且还可能会存在计数回绕的问题,如果次数超过计数器的范围,就会溢出。
布隆过滤器的应用
通常在如图情况中我们会使用布隆过滤器,不在的情况一定是准确的,但是在的话不一定准确,我们可以再到数据库中进行查找,不在的情况会大大提高效率
- 给两个文件,分别有100亿个query,我们只有1G内存,如何找到两个文件交集?分别给出精确算法和近似算法
query是一个查询语句的意思,一个query语句的大小平均约为30-60字节100亿个query大约占用300-600G,100亿个字节是10G
方案一(近似算法): 将文件1的query映射到布隆过滤器中,读取文件2中的query,判断是否在布隆过滤器中,在就是交集,是交集的一定在里面。(缺陷:交集中的数会不准确,因为有些数会误判,混到交集里面,判断在是不准确的,判断不在是准确的)
方案二(精确算法):
1.把文件1和文件2分别切分成A0、A1…A999和B0、B1…B999,两个文件分别切分成1000个小文件,因为哈希可能映射不是很均匀,然后将A0加载到内存放到unordered_set中,再依次和B0、B1…B999这10001个文件进行比较,然后加载A1到内存中,以此类推。用unordered_set的效率还是很高的,基本上是常数次。