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

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