位图概念
所谓位图,就是用每一位来存放某种状态,适用于海量数据,数据无重复的场景。通常是用来判断某个数据存不存在的。
例:给40个亿不重复的无符号整数,没排过序,给一个无符号整数,如何快速判断是否在这40亿个数中。
1G=2^30=10亿Byte,40亿整数占用空间=40x4Byte=160亿Byte=16G
但我们用char类型把每个bit位标识一个整数值,1Byte可以标识8个数,那么只需要16/4/8=0.5G=512MB
位图的应用
- 快速查找某个数据是否在一个集合中
- 排序+去重
- 操作系统中磁盘的标记等
缺点: 只能处理整形数据
size_t i = x / 8
计算x映射的bit在第i个char数组位置 size_t j = x % 8
计算x映射的bit在这个char的第j个比特位
代码实现设置bit位,以及重置bit位
set
把1左右j位,然后得到00…010…00,用这个数和整数进行按位或的操作
reset
把1左右j位,然后得到00…010…00,把这个数按位取反(避免改变其他操作),然后和第index个整数进行按位与的操作
void set(size_t x)
{
size_t i=x/8;
size_t j=x%8;
_bits[i] |= (1<<j);
}
void reset(size_t x)
{
size_t i=x/8;
size_t j=x%8;
_bits[i] &= ~(1<<j);
}
判断是否存在这个数
这个数的bit位置和第i个整数进行按位与的结果
bool test(size_t x)
{
int i=x/8;
int j=x%8;
return _bits[i] & (1<<j);
}
布隆过滤器概念
如何快速查找?
- 哈希表: 用哈希表存储用户的历史记录。缺点: 空间消耗比较大
- 位图: 用无图存储用户的历史记录。 缺点: 不能处理哈希冲突问题
- 布隆过滤器: 将哈希表和位图结合使用。
布隆过滤器实际上是一个很长的二进制向量和一系列随机映射函数。它通过多个哈希函数将一个数据映射到位图的结构中(也就是一个数据映射位图的多个位置,这样就可以减少冲突的概率)。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。
这里放置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的效率还是很高的,基本上是常数次。