Bloom_Filter
概述
布隆过滤器实际上和位图是类似的,它也主要负责利用比特位映射地址来存储数据,以实现对数据是否存在的快速判断。但与位图不同的是,位图只能映射无符号整型,尽管负数也可以转换为正数后映射,但对于浮点数、字符串、自定义类型等位图并无法进行映射,故需要将它们转换为无符号整型才能再次映射。但这样的转换冲突的概率较大,而布隆过滤器则是采用多种不同的哈希算法计算出多个不同的映射位进行多位映射,这样可以有效减少冲突概率。
这里以三个不同的映射方式,两个元素举例:
一个元素映射三个位置,尽管两个元素有一个位置映射相同,但并无大碍。若想造成误判,必须使得三个元素全部冲突,而这种概率是极低的。显而易见,若映射的方法越多,则冲突的概率越低。若开辟的空间越大,冲突的概率越低。
类的实现
成员变量
其主要是在位图之上进一步优化实现的,故底层只需要封装一层位图。且需要传入不同的哈希函数以完成单元素对多位置的映射,这里以 string 举例:
namespace Thepale
{
template <size_t N,
class HashFunc1 = String_Hash::BKDRHash,
class HashFunc2 = String_Hash::APHash,
class HashFunc3 = String_Hash::DJBHash,
class K = std::string>
class BloomFilter
{
protected:
std::bitset<N> _bs;
};
}
三个哈希函数分别采用了 BKDR 哈希,AP 哈希和 DJB 哈希:
namespace String_Hash
{
// BKDR Hash Function
struct BKDRHash
{
size_t operator() (const std::string& str)
{
size_t seed = 131; // 31 131 1313 13131 131313 etc..
size_t hash = 0;
for (auto& e : str)
{
hash = hash * seed + e;
}
return (hash & 0x7FFFFFFF);
}
};
// AP Hash Function
struct APHash
{
size_t operator() (const std::string& str)
{
size_t hash = 0;
int i = 0;
for (auto& e : str)
{
if ((i & 1) == 0)
{
hash ^= ((hash << 7) ^ e ^ (hash >> 3));
}
else
{
hash ^= (~((hash << 11) ^ e ^ (hash >> 5)));
}
i++;
}
return (hash & 0x7FFFFFFF);
}
};
// DJB Hash Function
struct DJBHash
{
size_t operator() (const std::string& str)
{
size_t hash = 5381;
for (auto& e : str)
{
hash += (hash << 5) + e;
}
return (hash & 0x7FFFFFFF);
}
};
}
成员函数
test
test 在布隆过滤器中是十分重要的,可知布隆过滤器判断元素是否存在是有出错概率的,但一个特殊的属性是,如果一个元素判断为不在,那么它必然不在。布隆过滤器仅仅是对 “在” 存在误判,对 “不在” 是不存在误判的。再次强调,如果对应的位置没有该元素,那么一定不会误判。如果难以理解,这大概可以解释为,看见你的人都会死掉,如果你发现有一个人没有死,那么他一定没有看见你,但是如果一个人死了,不一定是因为看见你而死的。(或许不太恰当,我很抱歉)
而 test 的判断在布隆过滤器中需要进行多次,有多少个哈希函数就需要判断多少次:
bool test(const K& key)
{
size_t pos1 = HashFunc1()(key) % N;
size_t pos2 = HashFunc2()(key) % N;
size_t pos3 = HashFunc3()(key) % N;
return _bs.test(pos1) && _bs.test(pos2) && _bs.test(pos3);
}
set
set 现在需要进行多次,有多少个哈希函数就需要映射到多少个位置:
void set(const K& key)
{
size_t pos1 = HashFunc1()(key) % N;
size_t pos2 = HashFunc2()(key) % N;
size_t pos3 = HashFunc3()(key) % N;
_bs.set(pos1);
_bs.set(pos2);
_bs.set(pos3);
}
由于这里的 set 不会由于越界导致超出范围(因为模了 N),故 set 不可能失败,故不需要返回值。
reset 是无法实现的,因为可能存在多个值映射到一个位置的情况,若贸然删除可能会影响其它值。可以采用计数的方法解决这一问题,通过计数器标识当前比特位有多少个值映射,但这需要付出几倍之多的空间代价,故并不能完美的解决问题,只是极端情况下的一种方案。
整体实现
#pragma once
#include <iostream>
#include <bitset>
#include <string>
namespace String_Hash
{
// BKDR Hash Function
struct BKDRHash
{
size_t operator() (const std::string& str)
{
size_t seed = 131; // 31 131 1313 13131 131313 etc..
size_t hash = 0;
for (auto& e : str)
{
hash = hash * seed + e;
}
return (hash & 0x7FFFFFFF);
}
};
// AP Hash Function
struct APHash
{
size_t operator() (const std::string& str)
{
size_t hash = 0;
int i = 0;
for (auto& e : str)
{
if ((i & 1) == 0)
{
hash ^= ((hash << 7) ^ e ^ (hash >> 3));
}
else
{
hash ^= (~((hash << 11) ^ e ^ (hash >> 5)));
}
i++;
}
return (hash & 0x7FFFFFFF);
}
};
// DJB Hash Function
struct DJBHash
{
size_t operator() (const std::string& str)
{
size_t hash = 5381;
for (auto& e : str)
{
hash += (hash << 5) + e;
}
return (hash & 0x7FFFFFFF);
}
};
}
namespace Thepale
{
template <size_t N,
class HashFunc1 = String_Hash::BKDRHash,
class HashFunc2 = String_Hash::APHash,
class HashFunc3 = String_Hash::DJBHash,
class K = std::string>
class BloomFilter
{
public:
void set(const K& key)
{
size_t pos1 = HashFunc1()(key) % N;
size_t pos2 = HashFunc2()(key) % N;
size_t pos3 = HashFunc3()(key) % N;
_bs.set(pos1);
_bs.set(pos2);
_bs.set(pos3);
}
bool test(const K& key)
{
size_t pos1 = HashFunc1()(key) % N;
size_t pos2 = HashFunc2()(key) % N;
size_t pos3 = HashFunc3()(key) % N;
return _bs.test(pos1) && _bs.test(pos2) && _bs.test(pos3);
}
//无法实现删除 - 可能导致误判
//引入计数可以一定程度实现
protected:
std::bitset<N> _bs;
};
}