哈希表的另外一个应用是布隆过滤器。
1.什么是布隆过滤器
关于布隆过滤器,我们需要讲很多关于它的东西,首先我们可以知道这个东西是个叫做布隆的人发现的,它一般用于解决网络爬虫重复问题,还有比如防垃圾邮件或垃圾网页的实现,都可以利用它。
布隆过滤器也是针对的是哈希冲突,可以通过一个Hash函数将一个元素映射成一个位阵列(Bit Array)中的一个点。这样一来,我们只要看看这个点是不是 1 就知道可以集合中有没有它了。这就是布隆过滤器的基本思想。
通俗点说也就是本来是一个位表示存在还是不存在,为了方便解决冲突问题,我们在这里采用多个位来表示一个数的状态,在这有个特别重要的特点:布隆中不存在是确定的,存在是不一定的,因为多个位置表示一个数,如果这些位当中有一位是0,那么这个数一定是不存在的,但是如果这个数对应的位置都是1,不一定代表他存在,因为也可能是其它数的位是1,影响了它。
2.布隆过滤器的优缺点
布隆的优点:是空间效率和查询时间都远远超过一般的算法,布隆过滤器存储空间和插入 / 查询时间都是常数O(1)。另外, 散列函数相互之间没有关系,方便由硬件并行实现。布隆过滤器不需要存储元素本身,因为它所存储的是它数据的状态,在某些对保密要求非常严格的场合有优势。
布隆过滤器的缺点:误算率是其中之一。随着存入的元素数量增加,误算率随之增加。但是如果元素数量太少,则使用散列表足矣。
(误判补救方法是:再建立一个小的白名单,存储那些可能被误判的信息。)
另外,一般情况下不能从布隆过滤器中删除元素. 我们很容易想到把位数组变成整数数组,每插入一个元素相应的计数器加 1, 这样删除元素时将计数器减掉就可以了。然而要保证安全地删除元素并非如此简单。首先我们必须保证删除的元素的确在布隆过滤器里面. 这一点单凭这个过滤器是无法保证的。另外计数器回绕也会造成问题。我们也可以使用整形来解决这个问题,当插入以后,整形++就好,这个时候即能代表整形存在也能维护引用计数。
总结
在计算机科学中,我们常常会碰到时间换空间或者空间换时间的情况,即为了达到某一个方面的最优而牺牲另一个方面。Bloom Filter在时间空间这两个因素之外又引入了另一个因素:错误率。在使用Bloom Filter判断一个元素是否属于某个集合时,会有一定的错误率。也就是说,有可能把不属于这个集合的元素误认为属于这个集合(False Positive),但不会把属于这个集合的元素误认为不属于这个集合(False Negative)。在增加了错误率这个因素之后,Bloom Filter通过允许少量的错误来节省大量的存储空间。
自从Burton Bloom在70年代提出Bloom Filter之后,Bloom Filter就被广泛用于拼写检查和数据库系统中。
代码实现:
#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include<iostream>
#include<cstdlib>
#include<cassert>
#include"BitMap.hpp"
using namespace std;
template <typename K>
struct _Funcpos1
{
size_t BKDRHash(const char *str)
{
register size_t hash = 0;
while (size_t ch = (size_t)*str++)
{
hash = hash * 131 + ch; // 也可以乘以31、131、1313、13131、131313..
// 有人说将乘法分解为位运算及加减法可以提高效率,如将上式表达为:hash = hash << 7 + hash << 1 + hash + ch;
// 但其实在Intel平台上,CPU内部对二者的处理效率都是差不多的,
// 我分别进行了100亿次的上述两种运算,发现二者时间差距基本为0(如果是Debug版,分解成位运算后的耗时还要高1/3);
// 在ARM这类RISC系统上没有测试过,由于ARM内部使用Booth's Algorithm来模拟32位整数乘法运算,它的效率与乘数有关:
// 当乘数8-31位都为1或0时,需要1个时钟周期
// 当乘数16-31位都为1或0时,需要2个时钟周期
// 当乘数24-31位都为1或0时,需要3个时钟周期
// 否则,需要4个时钟周期
// 因此,虽然我没有实际测试,但是我依然认为二者效率上差别不大
}
return hash;
}
size_t operator()(const string& key)
{
return BKDRHash(key.c_str());
}
};
template<typename K>
struct _Funcpos2
{
size_t SDBMHash(const char *str)
{
register size_t hash = 0;
while (size_t ch = (size_t)*str++)
{
hash = 65599 * hash + ch;
}
return hash;
}
size_t operator()(const string& key)
{
return SDBMHash(key.c_str());
}
};
template<typename K>
struct _Funcpos3
{
size_t RSHash(const char *str)
{
register size_t hash = 0;
size_t magic = 63689;
while (size_t ch = (size_t)*str++)
{
hash = hash * magic + ch;
magic *= 378551;
}
return hash;
}
size_t operator()(const string& key)
{
return RSHash(key.c_str());
}
};
template<typename K>
struct _Funcpos4
{
size_t RSHash(const char *str)
{
register size_t hash = 0;
size_t ch;
for (long i = 0; ch = (size_t)*str++; i++)
{
if ((i & 1) == 0)
{
hash ^= ((hash << 7) ^ ch ^ (hash >> 3));
}
else
{
hash ^= (~((hash << 11) ^ ch ^ (hash >> 5)));
}
}
return hash;
}
size_t operator()(const string& key)
{
return RSHash(key.c_str());
}
};
template<typename K>
struct _Funcpos5
{
size_t RSHash(const char *str)
{
if (!*str) // 这是由本人添加,以保证空字符串返回哈希值0
return 0;
register size_t hash = 1315423911;
while (size_t ch = (size_t)*str++)
{
hash ^= ((hash << 5) + ch + (hash >> 2));
}
return hash;
}
size_t operator()(const string& key)
{
return RSHash(key.c_str());
}
};
template<typename K = string,
typename Funcpos1 = _Funcpos1<K>,
typename Funcpos2 = _Funcpos2<K>,
typename Funcpos3 = _Funcpos3<K>,
typename Funcpos4 = _Funcpos4<K>,
typename Funcpos5 = _Funcpos5<K> >
class BloomFilter
{
public:
//构造函数
BloomFilter(const size_t range)
: _size(range)
, _bl(range)
{
}
//布隆过滤器进行设置,根据原理,对通过字符串函数所计算的K个位进行设置状态。
void Set(const K& key)
{
size_t index1 = Funcpos1()(key.c_str())%_size;
size_t index2 = Funcpos2()(key.c_str())%_size;
size_t index3 = Funcpos3()(key.c_str())%_size;
size_t index4 = Funcpos4()(key.c_str())%_size;
size_t index5 = Funcpos5()(key.c_str())%_size;
_bl.Set(index1);
cout << index1 << endl;
_bl.Set(index2);
cout << index2 << endl;
_bl.Set(index3);
cout << index3 << endl;
_bl.Set(index4);
cout << index4 << endl;
_bl.Set(index5);
cout << index5 << endl<<endl;
}
bool Test(const K& key)
{
size_t index1 = Funcpos1()(key.c_str() )% _size;
if (_bl.test(index1) == 0)
{
return false;
}
size_t index2 = Funcpos2()(key.c_str()) % _size;
if (_bl.test(index2) == 0)
{
return false;
}
size_t index3 = Funcpos3()(key.c_str()) % _size;
if (_bl.test(index3) == 0)
{
return false;
}
size_t index4 = Funcpos4()(key.c_str() )% _size;
if (_bl.test(index4) == 0)
{
return false;
}
size_t index5 = Funcpos5()(key.c_str() )% _size;
if (_bl.test(index5) == 0)
{
return false;
}
return true;
}
protected:
BitMap _bl;
size_t _size;
};
关于位图的实现参考我上一篇关于位图的博客。
代码已上传github:https://github.com/wsy081414/data-structure/tree/master/%E5%93%88%E5%B8%8C%E8%A1%A8