布隆过滤器
- 一 概念
- 1.1布隆过滤器的提出
- 2.概念
- 二 模拟实现
- 2.1 三个仿函数
- set
- Test
- 全代码
- 三 实际应用
一 概念
1.1布隆过滤器的提出
我们在使用新闻客户端看新闻时,它会给我们不停地推荐新的内容,它每次推荐时要去重,去掉那些已经看过的内容。问题来了,新闻客户端推荐系统如何实现推送去重的? 用服务器记录了用户看过的所有历史记录,当推荐系统推荐新闻时会从每个用户的历史记录里进行筛选,过滤掉那些已经存在的记录。 如何快速查找呢?
- 用哈希表存储用户记录,缺点:浪费空间
- 用位图存储用户记录,缺点:位图一般只能处理整形,如果内容编号是字符串就无法处理了。
- 将哈希与位图结合,即布隆过滤器
就像是我们在逛淘宝的时候,如此多的信息是如何被处理的?这就是我们要学习的布隆过滤器。
2.概念
布隆过滤器是由布隆(Burton Howard Bloom)在1970年提出的 一种紧凑型的、比较巧妙的概率型数据结构,特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”,它是用多个哈希函数,将一个数据映射到位图结构中。此种方式不仅可以提升查询效率,也可以节省大量的内存空间。
我们用图来解释这个概念。
用位图的方式(整型代替字符串),一个字符串对应一个,但是如图出现了冲突
我们对这个冲突进行一下讨论,
- 对于在的值:可能会有误判
- 对于不在的值:这个就是准确的
看来一个值映射一个位置无法做到识别。所以布隆过滤器就是一个值映射多个位置如图
可以看到一个值去映射多个位置可以大大降低冲突的概率,这也应征了概念的话 ” 某样东西一定不存在或者可能存在 “
二 模拟实现
上面说了布隆过滤器的精髓就在于一个值映射多个位置我们的模拟实现用映射三个位置来实现。
2.1 三个仿函数
struct Hashfunc1
{
size_t operator()(const string& s)
{
size_t hash = 0;
for (auto i : s)
{
hash += i;
}
return hash;
}
};
struct Hashfunc2
{
size_t operator()(const string& s)
{
size_t hash = 0;
for (size_t i = 0; i < s.size(); i++)
{
if ((i & 1) == 0) // 偶数位字符
{
hash ^= ((hash << 7) ^ (s[i]) ^ (hash >> 3));
}
else // 奇数位字符
{
hash ^= (~((hash << 11) ^ (s[i]) ^ (hash >> 5)));
}
}
return hash;
}
};
struct Hashfunc3
{
size_t operator()(const string& s)
{
size_t hash = 5381;
for (auto ch : s)
{
hash = hash * 33 ^ ch;
}
return hash;
}
};
这些我们都可以去网站上随便挑选几个写入即可各种字符串的Hash函数
对于布隆过滤器中他的大小要设置成多少,这也是个数学问题,可以参考此文章
布隆过滤器长度
我这里就把他设置成5;
set
void Set(const K& key)
{
size_t hash1 = Hash1()(key) % M;
size_t hash2 = Hash2()(key) % M;
size_t hash3 = Hash3()(key) % M;
_bs->set(hash1);
_bs->set(hash2);
_bs->set(hash3);
}
Test
bool Test(const K& key)
{
size_t hash1 = Hash1()(key) % M;
if (_bs->test(hash1) == false)
{
return false;
}
size_t hash2 = Hash2()(key) % M;
if (_bs->test(hash2) == false)
{
return false;
}
size_t hash3 = Hash3()(key) % M;
if (_bs->test(hash3) == false)
{
return false;
}
return true;
}
全代码
struct Hashfunc1
{
size_t operator()(const string& s)
{
size_t hash = 0;
for (auto i : s)
{
hash += i;
}
return hash;
}
};
struct Hashfunc2
{
size_t operator()(const string& s)
{
size_t hash = 0;
for (size_t i = 0; i < s.size(); i++)
{
if ((i & 1) == 0) // 偶数位字符
{
hash ^= ((hash << 7) ^ (s[i]) ^ (hash >> 3));
}
else // 奇数位字符
{
hash ^= (~((hash << 11) ^ (s[i]) ^ (hash >> 5)));
}
}
return hash;
}
};
struct Hashfunc3
{
size_t operator()(const string& s)
{
size_t hash = 5381;
for (auto ch : s)
{
hash = hash * 33 ^ ch;
}
return hash;
}
};
template<size_t N,
class K = string,
class Hash1 = Hashfunc1,
class Hash1 = Hashfunc2,
class Hash1 = Hashfunc3>
class BloomFilter
{
void Set(const K& key)
{
size_t hash1 = Hash1()(key) % M;
size_t hash2 = Hash2()(key) % M;
size_t hash3 = Hash3()(key) % M;
_bs->set(hash1);
_bs->set(hash2);
_bs->set(hash3);
}
bool Test(const K& key)
{
size_t hash1 = Hash1()(key) % M;
if (_bs->test(hash1) == false)
{
return false;
}
size_t hash2 = Hash2()(key) % M;
if (_bs->test(hash2) == false)
{
return false;
}
size_t hash3 = Hash3()(key) % M;
if (_bs->test(hash3) == false)
{
return false;
}
return true;
}
private:
static const size_t M = 5 * N;
bitset<M> _bs;
};
注意,布隆过滤器不好做删除操作,当删除一个数之后,别的数出现误判的可能性会增大。
三 实际应用
给两个文件,分别有100亿个query,我们只有1G内存,如何找到两个文件交集?分别给出精确算法
假设一个query有50个字节,100亿个query占500g的内存。这时候就用到了哈希切分
相同query,一定会进入编号相同的文件中。再把Ai和Bi的query分别放入set中,再在这个set中找交集。