Bloom-Filter(布隆過濾器)
判斷一個元素是否在一個集合中,我們平常的算法,肯定就是遍歷比較了,這樣對小數量數據還可以,但對海量數據肯定是不適用的,就算用二叉樹騎士劍複雜度也是O(logn),Burton Bloom在1970年提出了Bloom Filter算法,騎士劍複雜度爲O(1)
但這個算法不能保證100%正確,所以不適合哪些‘零錯誤’的應用場合。
Bloom-Filter的基本思想:
用一個哈希算法(hash函數)將一個元素集合映射到一個二進制位數組(維數組)中的某一位。如果改爲已經被置爲1,那麼表示元素已經存在。爲了減少hash衝突問題,所以引用了多個hash函數,如果通過其中的一個hash值得出某元素不在集合中,那麼該元素肯定不在集合中。只有所有的hash函數告訴我們該元素在集合中時,才能確定該元素在集合中。
1,首先要有表示集合的數據結構,在Bloom-Filter中,使用的是一個二進制數組(位數組)
假設Bloom Filter使用m比特的數組來保存信息,初始狀態時,Bloom Filter是一個包含m位的位數組,每一位都置爲0
2,如果我們現在有一個集合S={x1,x2,x3,…,xn},包含n個元素。現在我們需要K個hash函數,對n個元素進行並銀蛇到我們的位數組中。這樣計算K的公式爲:
k=ln2*(m/n)
m:bit數組的寬度(bit數)
n:集合中元素的個數
k:使用的hash函數的個數
最優的hash函數的個數 = ln2 * (數組大小/元素個數)
當我們往Bloom Filter中增加任意一個元素x時候,我們使用K個hash函數得到K個hash值,然後將數組中對應的比特位設置爲1.即第i個hash函數映射的位置hashi(x)就會被置爲1(1≤ i ≤k),注意,如果一個位置多次被置爲1,那麼只有第一次騎作用,後面幾次將沒有任何效果。在下圖中,k=3,且有兩個hash函數選中同一位置
3,現在我們就可以判斷一個元素是否在這個集合中了,比如判斷y是否在這個集合中,我們只需要對y是否在這個集合中,我們只需要對y使用k個hash函數得到k個hash值,如果所有hashi(y)的位置都是1,(1≤ i ≤k),即k個位置都被置爲1了,那麼我們就認爲y是集合中的元素,否則就認爲y不是集合中的元素。下圖中y1就不是集合中的元素(因爲1有一處指向了’0‘爲)。y2或者屬於這個集合。或者剛好是個false positive(假陽性)
顯然這個判斷並不保證查找的結果是100%正確的,這個假陽性比率的計算公式爲:
//BloomFilter
class BloomFilter
{
//bit數組的寬度(bit數)
protected $m;
//使用hash函數的個數
protected $k;
//集合中元素的個數
protected $n;
//二進制數組
protected $bitSet;
//構造函數初始化
public function __construct($m,$k)
{
$this->m=$m;
$this->k=$k;
$this->n=0;
//初始化二進制數組全部爲0
$this->bitSet = array_fill(0,$this->m - 1,false);
}
//計算最有的hash函數個數:當hash函數個數k = (ln2)*(m/n)時錯誤率最小
public static function getHashCount($m,$n)
{
return ceil(($m/$n)*log(2));
}
//使用CRC32產生一個32bit的校驗值。
//由於CRC32產生校驗值時元數據塊的每一位bit都會被計算,所以數據塊中即使只有一位發生了變化,也會得到不同的CRC32值
protected static function getHashCode($string)
{
return crc32($string);
}
//對key元素進行K次堆積計算並返回其在二進制數組中的位置
protected function getSlots($key)
{
$slots = [];
$hash = self::getHashCode($key);
//用hash值做隨機種子,這樣既有一定的隨機性,對一樣的值,也有確定性
mt_srand($hash);
for($i=0;$i<$this->k;$i++)
{
$slots[] = mt_rand(0,$this->m - 1);
}
return $slots;
}
//添加一個元素,並計算集合長度n
public function add($key)
{
if(is_array($key)){
foreach ($key as $k)
{
$this->add($k);
}
return ;
}
$this->n++;
foreach ($this->getSlots($key) as $slot)
{
//將計算得到的位置置爲true;
$this->bitSet[$slot] = true;
}
}
//判斷某元素是否在集合中
public function contains($key)
{
if(is_array($key)){
//如果$key是數組,則判斷數組元素是否在集合中
foreach($key as $k)
{
if($this->contains($key) == false){
return false;
}
}
return true;
}
foreach ($this->getSlots($key) as $slot)
{
//判斷單個元素是否在集合中
if($this->bitSet[$slot] == false){
return false;
}
}
return true;
}
}
//定義一個集合
$items = ['first item','second_item','third_item'];
//定義一個Bloom Filter對象並將集合元素添加到過濾器中
$filter = new BloomFilter(100,BloomFilter::getHashCount(100,3));
$this->add($items);
//判斷items1中的元素是否在items集合中
$items1 = ['firstItem','secondItem','thirdItem'];
foreach ($items1 as $item)
{
var_dump($filter->contains($item));
}