Bloom Filter

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));
}

轉自https://www.jianshu.com/p/16e5f3481366

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章