1K=1024byte
1M=1024k=1024*1024byte(约100万个字节)
1G=1024M=1024*1024*1024byte(约为10亿个字节)
先看一个题,给40亿个不重复无符号整数,给一个无符号整数,快速判断一个数是否在这40亿个数中。
判断一个数在或者不在,我们通常有以下方法:
1.暴力查找 时间复杂度O(N)
2.排序完,进行二分查找O(N)+O(N lgN)
3.哈希/搜索树 哈希O(1)搜索树O(NlgN)
上面的可以看出,如果只查找一次,用暴力查找比较好,查多次用后面的较好。
但是上面的
1.暴力查找需要报40亿个整数都要加入磁盘中16G,存不下
2.排序也一样,存不下
3.如果用搜索树当然更不行,一个节点有左右孩子,数等,比16G还大,哈希桶也存不下,有一个指针,和数据
这时我们想到直接定址法的哈希,但是直接定地址法的哈希要开整形最大值那么大的整形的空间。42亿九千万个整形 大概也就是16G多,存不下,但是我们想到只让判断这个数在或者不在两种状态,用一个比特位就可以表示,没有必要 开一个整形,这样就不需要16G,只需16G/4/8=500M,这时我们的内存就可以存下,这就是所说的位图
位图:也是一种直接定址法的哈希。本质是用一个数组,但是用一个比特位来表示一种状态,0表示不存在,1表示存在
位图的具体实现如下:
#include<iostream>
#include<vector>
using namespace std;
class BitSet
{
public:
BitSet(size_t range)
{
_bits.resize((range >> 5) + 1, 0);//注意移位运算符,优先级特别低
}
void Set(size_t x)
{
size_t num = x >> 5;//知道在哪个字节
size_t pos = x % 32;//知道在哪个比特位
//把这一位置为1,给这一位或1,给其他位或0.0与任何数或为任何数
_bits[num] |= (1 << pos);
}
void ReSet(size_t x)
{
size_t num = x >> 5;
size_t pos = x % 32;
//1与任何数与为任何数,0与任何数与为0
_bits[num] &= ~(1 << pos);
}
bool Test(size_t x)
{
size_t num = x >> 5;
size_t pos = x % 32;
//如何测试这一位为1,还是0
return _bits[num] & (1 << pos);
}
protected:
vector<size_t >_bits;
};
int main()
{
//BitSet bs(-1);
BitSet bs(~0);
bs.Set(1);
bs.Set(19);
bs.Set(1000);
bs.ReSet(1000);
cout<<bs.Test(1)<<endl;
cout<<bs.Test(19)<<endl;
cout<<bs.Test(1000)<<endl;
system("pause");
return 0;
}
在40亿个字符串集合中,快速判断一个字符串在或者不在?
这个题我们也可以用位图,可是这是字符串又不是整数,可是我们可以把字符串通过在字符串哈希转换为整数。可是这时候会产生哈希冲突,会产生误判,一个数明明没在,却判断为在,于是我们可以将一个元素经过不同的哈希函数,映射到多个位置,如果这几个位置上都为1,我们就认为这个元素存在,如果有一个位为0,就表示不存在。这就是所谓的布隆。
布隆:是位图+字符串哈希的结合,基本思想是,通过一个哈希函数将一个元素映射到一个位置,我们只要判断这个位置是不是存在,就能判断是否存在,但是由于哈希冲突的原因,不同的元素经过哈希函数会映射到相同的哈希地址,导致误判,为了缓解误判,我们将一个元素经过多个散列函数映射到多个位置上,如果这多个位都存在,我们认为存在,如果有一个位不存在,则不存在。
注意:布隆过滤器是存在不准确,不存在准确。
1.怎样降低误判的概率?
多个哈希函数进行映射
具体实现代码如下:
#include<iostream>
#include<vector>
using namespace std;
class BitSet
{
public:
BitSet(size_t range)
{
_bits.resize((range >> 5) + 1, 0);//注意移位运算符,优先级特别低
}
void Set(size_t x)
{
size_t num = x >> 5;//知道在哪个字节
size_t pos = x % 32;//知道在哪个比特位
//把这一位置为1,给这一位或1,给其他位或0.0与任何数或为任何数
_bits[num] |= (1 << pos);
}
void ReSet(size_t x)
{
size_t num = x >> 5;
size_t pos = x % 32;
//1与任何数与为任何数,0与任何数与为任何数
_bits[num] &= ~(1 << pos);
}
bool Test(size_t x)
{
size_t num = x >> 5;
size_t pos = x % 32;
//如何测试这一位为1,还是0
return _bits[num] & (1 << pos);
}
protected:
vector<size_t >_bits;
};
struct _HashFunc1
{
size_t operator()(const string& str)
{
size_t num = 0;
for (int i = 0; i < str.size(); i++)
{
num = num * 131 + str[i];
}
return num;
}
};
struct _HashFunc2
{
size_t operator()(const string& str)
{
size_t num = 0;
for (int i = 0; i < str.size(); i++)
{
num = num * 65596 + str[i];
}
return num;
}
};
struct _HashFunc3
{
size_t operator()(const string& str)
{
size_t magic = 63689;
size_t num = 0;
for (int i = 0; i < str.size(); i++)
{
num = num*magic + str[i];
magic = magic * 378551;
}
return num;
}
};
struct _HashFunc4
{
size_t operator()(const string& str)
{
size_t num = 0;
for (int i = 0; i < str.size(); i++)
{
if ((i & 1) == 0)
{
num ^= ((num << 7) ^ str[i] ^ (num >> 3));
}
else
{
num ^= (~((num << 1) ^ str[i] ^ (num >> 5)));
}
}
return num;
}
};
class BloomFilter
{
public:
BloomFilter(size_t x)
:_bs(x)
, _capacity(x)
{}
void Set(const string & key)
{
size_t index1 = (_HashFunc1()(key)) % _capacity;
size_t index2 = (_HashFunc2()(key)) % _capacity;
size_t index3 = (_HashFunc3()(key)) % _capacity;
size_t index4 = (_HashFunc4()(key)) % _capacity;
_bs.Set(index1);
_bs.Set(index2);
_bs.Set(index3);
_bs.Set(index4);
}
int Test(const string& key)
{
size_t index1 = (_HashFunc1()(key)) % _capacity;
size_t index2 = (_HashFunc2()(key)) % _capacity;
size_t index3 = (_HashFunc3()(key)) % _capacity;
size_t index4 = (_HashFunc4()(key)) % _capacity;
//只有四个位都为1才存在
if (!_bs.Test(index1))
{
return false;
}
if (!_bs.Test(index2))
{
return false;
}
if (!_bs.Test(index2))
{
return false;
}
if (!_bs.Test(index2))
{
return false;
}
return true;
}
private:
BitSet _bs;
size_t _capacity;
};
int main()
{
BloomFilter bf(-1);
bf.Set("nihao");
bf.Set("wohenhao");
bf.Set("nihaoma");
bf.Set("nihao ");
cout<<bf.Test("nihao")<<endl;
cout<<bf.Test("wohenhao")<<endl;
cout<<bf.Test("nihaoma")<<endl;
cout<<bf.Test("nihao ")<<endl;
cout << bf.Test("nihao ") << endl;
system("pause");
return 0;
}
以上的代码没有对布隆进行删除操作?
2.如何对布隆过滤器进行删除操作
因为一个位可能对应着多个元素,所以当我们删除一个位的时候,不能直接删除,否则会影响其他元素。这时候我们采取引用计数的方式来实现删除操作,为了记录这个元素出现的次数,我们不能用一个位来表示状态了,我们可以用一个无符号整形来表示状态。这样就要用四个字节表示在或者不在。我们不支持这样做,因为我们用布隆就是为了解决避免空间的浪费,如果增加了,取消设置,违背了。
3.布隆过滤器的优点:空间效率和查询时间都很好,超过一般的算法。插入和查找0(1)
布隆过滤器的缺点:误判
4.布隆过滤器的应用场景:
爬虫爬某个网站是否被访问过(允许漏爬)。
检测一个英语单词拼写是否拼写正确(也就是判断它在某个已知的字典里)
破案当中一个嫌疑犯的名字是否已经在嫌疑犯名单上
过滤垃圾邮件
位图布隆对比:位图针对整形,布隆针对字符串。