布隆過濾器
布隆過濾器(Bloom filter)是一個高空間利用率數據結構,由Burton Bloom於1970年提出。被用於測試一個元素是否在集合中(由於集合無重複元素的性質,可用來判重)。
布隆過濾器的優勢
布隆過濾器的思想類似位圖,主要是由一個很長的二進制向量和若干個(k個)散列映射函數組成。因爲每個元數據的存儲信息值固定,而且總的二進制向量固定。所以在內存佔用和查詢時間上都遠遠超過一般的算法。當然存在一定的不準確率(可以控制)和不容易刪除樣本數據。
應用場景
-
黑名單:比如郵件黑名單過濾器,判斷郵件地址是否在黑名單中。
-
排序(僅限於 BitSet) 。
-
網絡爬蟲:判斷某個URL是否已經被爬取過。
布隆過濾器和位圖的區別
需要注意和位圖不同,布隆過濾器通常有多個散列映射函數,因爲傳入的通常是字符串,對於單獨的字符串例如‘abcd’和‘acbd’,單個散列映射函數計算的位相同,例如
當垃圾郵箱www.abcd.com已經標記的情況下,非垃圾郵箱www.acbd.com通過散列映射函數計算的地址和www.abcd.com相同,導致在誤判認爲www.acbd.com也是垃圾郵箱。
假如我們有多個地址來映射一個字符串就能大大減少這種情況。
布隆過濾器算法思想
- 需要n個散列映射函數,每個散列映射函數將傳入的字符串映射爲不同的key值
- 初始化,需要設置k個比特大小的數組用來映射(位圖)
- 某個key插入布隆過濾器,用散列函數計算出對應的比特位,並將其置爲1
- 判斷某個key是否在過濾器中,需要先計算對應的比特位,判斷是否都爲1,如果成立,說明在裏面,否則不在(存在誤判情況)
布隆算法優缺點
優點:不需要儲存key,節省空間
- 在能夠承受一定的誤判時,布隆過濾器比其他數據結構有這很大的空間優勢
缺點:
- 有誤判率,即存在假陽性(False Position),即不能準確判斷元素是否在集合中(補救方法:再建立一個白 名單,存儲可能會誤判的數據)
- 不能獲取元素本身
- 一般情況下不能從布隆過濾器中刪除元素
- 如果採用計數方式刪除,可能會存在計數迴繞問題 。
誤判產生的原因
如下圖假設有一個非垃圾郵箱www.dcba.com,經過計算的比特位都被標記,就會被誤判爲垃圾郵箱。
根據布隆過濾器的原理,一般情況下不能從布隆過濾器中刪除元素。
實現簡易布隆過濾器
到這裏,我們來實現一個簡易的布隆過濾器,不過有一個問題要解決,映射n個數字要開多大的空間?有一個公式k = (m/n)In用來計算,ln大約2.7,n是數據,m是開多少位,經過計算大約爲4到5倍左右,爲了簡單,我們取5倍。
位圖的代碼在筆者上一篇博客 數據結構-------位圖中說明。
以下爲簡易布隆過濾器的實現。
template<class K=string>
struct _Hanc1
{
size_t operator()(const K& str)
{
size_t hash = 0;
for (int i=0; i < str.size(); i++)
{
hash = str[i] * 769+ hash;
}
return hash;
}
};
template<class K = string>
struct _Hanc2
{
size_t operator()(const K& str)
{
size_t hash = 0;
for (int i=0; i < str.size(); i++)
{
hash = str[i] * 97 + hash;
}
return hash;
}
};
template<class K = string>
struct _Hanc3
{
size_t operator()(const K& str)
{
size_t hash = 0;
for (int i=0; i < str.size(); i++)
{
hash = str[i] * 389 + hash;
}
return hash;
}
};
template<class K = string
,class Hanc1 = _Hanc1<K>
,class Hanc2 = _Hanc2<K>
,class Hanc3 = _Hanc3<K>
>
class Bloomfilter
{
public:
Bloomfilter(size_t size )
:_bf(size*5)
,_size(size*5)
{}
void insert(const K& k)
{
size_t index1 = Hanc1()(k) % _size;
size_t index2 = Hanc2()(k) % _size;
size_t index3 = Hanc3()(k) % _size;
_bf.insert(index1);
_bf.insert(index2);
_bf.insert(index3);
}
bool test(const K& k)
{
size_t index1 = Hanc1()(k) % _size;
size_t index2 = Hanc2()(k) % _size;
size_t index3 = Hanc3()(k) % _size;
return _bf.findbit(index1)
&& _bf.findbit(index2)
&& _bf.findbit(index3);//如果返回真,可能是不準確的,返回假是準確的。
}
private:
BitMap _bf;
size_t _size;
};