我们有1千万个整数,整数的范围在1到1亿之间。如何快速查找某个整数是否在这1千万个整数中呢?
#include <iostream>
#include <cstring>
using namespace std;
class BitMap
{
char *bytes; //char是1字节,8位
int nbits;
public:
BitMap(int n)
{
nbits = n;
bytes = new char [nbits/8 + 1];
memset(bytes, 0, (nbits/8+1)*sizeof(char));
}
~BitMap()
{
delete [] bytes;
}
void set(int k)
{
if(k > nbits)
return;
int byteIndex = k/8;
int bitIndex = k%8;
bytes[byteIndex] |= (1<<bitIndex);
}
bool get(int k)
{
if(k > nbits)
return false;
int byteIndex = k/8;
int bitIndex = k%8;
return (bytes[byteIndex] & (1 << bitIndex)) != 0;
}
void print()
{
for(int i = 15; i >= 0; --i)
cout << get(i) << " ";
}
};
int main()
{
BitMap bm(8);
bm.set(8);
cout << bm.get(8) << endl;
bm.print();
return 0;
}
比如上面例子,如果用散列表存储这1千万的数据,数据是32位的整型数,也就是需要4个字节的存储空间,那总共至少需要40MB的存储空间。如果通过位图的话,数字范围在1到1亿之间,只需要1亿个二进制位,1亿/8/1024/1024 = 12, 也就是12MB左右的存储空间就够了。
不过,这里我们有个假设,就是数字范围不是很大。如果数字的范围很大,数字范围不是1到1亿,而是1到10亿,那位图的大小就是10亿个二进制位,也就是120MB的大小,消耗的内存空间,不降反增!
怎么办?请布隆过滤器登场!
还是刚刚那个例子,数据个数是1千万,数据的范围是1到10亿。
对于两个不同的数字,经过 K 个哈希函数处理之后,K 个哈希值都相同的概率就非常低了。尽管采用 K 个哈希函数之后,两个数字哈希冲突的概率降低了,但是,这种处理方式又带来了新的问题,那就是容易误判。看下面例子。
布隆过滤器非常适合这种不需要100%准确的、允许存在小概率误判的大规模判重场景。比如统计一个大型网站的每天的UV数,也就是每天有多少用户访问了网站,就可以使用布隆过滤器,对重复访问的用户,进行去重。
布隆过滤器的误判率,主要跟哈希函数的个数、位图的大小有关。往布隆过滤器中不停地加入数据之后,位图中不是true的位置就越来越少了,误判率就越来越高了。所以,对于无法事先知道要判重的数据个数的情况,我们需要支持自动扩容的功能。
当布隆过滤器中,数据个数与位图大小的比例超过某个阈值的时候,我们就重新申请一个新的位图。后面来的新数据,会被放置到新的位图中。但是,如果我们要判断某个数据是否在布隆过滤器中已经存在,我们就需要查看多个位图,相应的执行效率就降低了一些。
位图、布隆过滤器应用如此广泛,很多编程语言都已经实现了。比如 Java 中的 BitSet 类就是一个位图,Redis 也提供了 BitMap 位图类,Google 的 Guava 工具包提供了BloomFilter 布隆过滤器的实现。
课后思考 1.假设我们有1亿个整数,数据范围是从1到10亿,如何快速并且省内存地给这1亿个数据从小到大排序?
传统做法:1亿个整数,存储需要400M空间 位图算法:数字范围是1到10亿,用位图存储125M就够了,然后将1亿个数字依次添加到位图中,再将位图按下标从小到大输出值为1的下标,排序就完成了,时间复杂度为O(n)