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