大家都玩过王者荣耀这一款游戏吧!在游戏中,我们可能会修改自己的游戏 id。这个 id 有一些要求,其中之一就是不能重复。那我们怎样才能快速判断一字符串是否已经存在呢?
在这之前我们学习过哈希表,可以将一个字符串通过字符串的哈希算法转化成整形,然后映射到哈希表中。哈希表能否用来解决这个问题呢?显然是不能的,因为无论你怎样选择字符串的哈希算法,在海量数据之下,两个字符串转化出来的整形是很可能会相同的。
那应该怎么解决这个问题呢!布隆这个人想到了一种降低判断字符串在还是不在的误判率的办法,那就是将同一字符串经过多个哈希算法进行整形转换。当判断一个字符串在不在的时候,当这个字符串经过这些哈希算法计算出来的数字在位图结构中均为 1 表示其可能存在;在位图结构中有一个不为 1 那么就可判定他不在。
由此可见,这种方案能够有效降低字符串判断在的误判率,因为经过了多个哈希算法的处理,判断字符串在的时候,需要转换出来的整形值在位图结构中全部为 1。
并且这种方法能够准确判断一个字符串不存在。
因此,一开始的问题就能得到解决(这只是展示一种解决方案,实际肯定不是这么做的)
可以想象一个场景,当你不需要严格准确判定的话,我们根本可以不用去数据库中查找,直接告诉用户这个昵称已经存在了。这样整体的速度就相当快了。尽管这样做可能导致有一些昵称虽然没有被注册,却依然提示用户被注册过了!可是,用户怎么知道呢!!
布隆过滤器是由布隆(Burton Howard Bloom)在1970年提出的 一种紧凑型的、比较巧妙的概率型数据结构,特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”,它是用多个哈希函数,将一个数据映射到位图结构中。此种方式不仅可以提升查询效率,也可以节省大量的内存空间。
如果你的布隆过滤器要判断的类型不是 string,一定要传入对应数量的哈希函数,默认的哈希函数只是针对 string 类型的嘛!
#include
#include
struct BKDRHash
{
size_t operator()(const string& str)
{
size_t hash = 0;
for(auto e : str) hash += hash * 131 + e;
return hash;
}
};
struct APHash
{
size_t operator()(const string& str)
{
size_t hash = 0;
for(int i = 0; i < str.size(); i++)
{
size_t ch = str[i];
if((i & 1) == 0)
{
hash ^= ((hash << 7) ^ ch ^ (hash >> 3));
}
else
{
hash ^= (~((hash << 11) ^ ch ^ (hash >> 5)));
}
}
return hash;
}
};
struct DJBHash
{
size_t operator()(const string& str)
{
size_t hash = 5381;
for(auto ch : str)
{
hash += (hash << 5) + ch;
}
return hash;
}
};
template<size_t N, class K = string,
class Hash1 = BKDRHash,
class Hash2 = APHash,
class Hash3 = DJBHash>
class BloomFilter
{
public:
private:
bitset<N> _bs; //底层数据结构:位图
};
这个函数用来向布隆过滤器中插入数据。参数是插入的数据。实现的方法很简单哈。将传入的参数依次传入三个哈希函数,得到三个整形值之后在对位图的大小取模,然后分别将底层数据结构位图的对应位置置位一即可。
下图中假设位图的大小是 8,然后我们插入两个字符串:baidu,tencent
void Set(const K& key)
{
size_t hash1 = Hash1()(key) % N;
_bs.set(hash1);
size_t hash2 = Hash2()(key) % N;
_bs.set(hash2);
size_t hash3 = Hash3()(key) % N;
_bs.set(hash3);
}
Hash1()(key)
这个代码的意思是:创建一个匿名对象,通过这个匿名对象来调用 operator()
。
这个函数用来判断一个字符串是否在布隆过滤器中。大体逻辑和向布隆过滤器中插入数据是一样的。只不过在依次调用哈希函数的过程中,我们就可以对结果做一次判断啦!如果通过哈希函数得到的整形值不在位图中(即位图的 test 接口返回 false )。
那么我们就可以直接得出结论:这个 key 不在布隆过滤器中,因为判断一个元素在布隆过滤器的条件是,这个 key 经过任意一个哈希函数得到的整形值在位图中的相应位置都是 1。
如果到函数末尾都没有返回 flase 那么,就可以返回 true 啦!虽然返回 true 的这个结果并不准确!
bool Test(const K& key)
{
size_t hash1 = Hash1()(key) % N;
if(_bs.test(hash1) == false) //每调用依次哈希函数就对位图做一次判断,看是否能直接返回结果
return false;
size_t hash2 = Hash2()(key) % N;
if(_bs.test(hash2) == false)
return false;
size_t hash3 = Hash3()(key) % N;
if(_bs.test(hash3) == false)
return false;
return true;
}
int main()
{
BloomFilter<95> bl;
bl.Set("猪八戒");
bl.Set("孙悟空");
bl.Set("唐僧");
bl.Set("沙悟净");
bl.Set("牛魔王");
bl.Set("铁扇公主");
cout << "猪八戒在不在哇:" << bl.Test("猪八戒") << endl;
cout << "孙悟空在不在哇:" << bl.Test("孙悟空") << endl;
cout << "唐僧在不在哇:" << bl.Test("唐僧") << endl;
cout << "芭蕉公主在不在哇:" << bl.Test("芭蕉公主") << endl;
return 0;
}