【数据结构】布隆过滤器

布隆过滤器(Bloom Filter)是由布隆(Burton Howard Bloom)在1970年提出的。
它实际上是由一个很长的二进制向量一系列随机映射函数组成,布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率删除困难但是没有识别错误的情形

在日常生活中,包括在设计计算机软件时,我们经常要判断一个元素是否在一个集合中。比如在字处理软件中,需要检查一个英语单词是否拼写正确(也就是要判断 它是否在已知的字典中);在 FBI,一个嫌疑人的名字是否已经在嫌疑名单上;在网络爬虫里,一个网址是否被访问过等等。最直接的方法就是将集合中全部的元素存在计算机中,遇到一个新元素时,将它和集合中的元素直接比较即可。一般来讲,计算机中的集合是用哈希表(hash table)来存储的。它的好处是快速准确,缺点是费存储空间。当集合比较小时,这个问题不显著,但是当集合巨大时,哈希表存储效率低的问题就显现出来 了。比如说,一个象 Yahoo,Hotmail 和 Gmai 那样的公众电子邮件(email)提供商,总是需要过滤来自发送垃圾邮件的人(spamer)的垃圾邮件。一个办法就是记录下那些发垃圾邮件的 email 地址。由于那些发送者不停地在注册新的地址,全世界少说也有几十亿个发垃圾邮件的地址,将他们都存起来则需要大量的网络服务器。如果用哈希表,每存储一亿 个 email 地址, 就需要 1.6GB 的内存(用哈希表实现的具体办法是将每一个 email 地址对应成一个八字节的信息指纹(详见:googlechinablog.com/2006/08/blog-post.html), 然后将这些信息指纹存入哈希表,由于哈希表的存储效率一般只有 50%,因此一个 email 地址需要占用十六个字节。一亿个地址大约要 1.6GB, 即十六亿字节的内存)。因此存贮几十亿个邮件地址可能需要上百 GB 的内存。除非是超级计算机,一般服务器是无法存储的。

基本概念

如果想判断一个元素是不是在一个集合里,一般想到的是将所有元素保存起来,然后通过比较确定。链表,树等等数据结构都是这种思路. 但是随着集合中元素的增加,我们需要的存储空间越来越大,检索速度也越来越慢。不过世界上还有一种叫作散列表(又叫哈希表,Hash table)的数据结构。它可以通过一个Hash函数将一个元素映射成一个位阵列(Bit Array)中的一个点。这样一来,我们只要看看这个点是不是 1 就知道可以集合中有没有它了。这就是布隆过滤器的基本思想。

Hash面临的问题就是冲突。假设 Hash 函数是良好的,如果我们的位阵列长度为 m 个点,那么如果我们想将冲突率降低到例如 1%,这个散列表就只能容纳 m/100 个元素。显然这就不叫空间有效了(Space-efficient)。解决方法也简单,就是使用多个 Hash,如果它们有一个说元素不在集合中,那肯定就不在。如果它们都说在,虽然也有一定可能性它们在说谎,不过直觉上判断这种事情的概率是比较低的。

优点

相比于其它的数据结构,布隆过滤器在空间和时间方面都有巨大的优势。布隆过滤器存储空间和插入/查询时间都是常数。另外,Hash 函数相互之间没有关系,方便由硬件并行实现。布隆过滤器不需要存储元素本身,在某些对保密要求非常严格的场合有优势。

布隆过滤器可以表示全集,其它任何数据结构都不能;

k 和 m 相同,使用同一组 Hash 函数的两个布隆过滤器的交并差运算可以使用位操作进行。

缺点

但是布隆过滤器的缺点和优点一样明显。误算率(False Positive)是其中之一。随着存入的元素数量增加,误算率随之增加。但是如果元素数量太少,则使用散列表足矣。

另外,一般情况下不能从布隆过滤器中删除元素. 我们很容易想到把位列阵变成整数数组,每插入一个元素相应的计数器加1,这样删除元素时将计数器减掉就可以了。然而要保证安全的删除元素并非如此简单。首先我们必须保证删除的元素的确在布隆过滤器里面. 这一点单凭这个过滤器是无法保证的。另外计数器回绕也会造成问题。

#pragma once 

#include"bitmap.hpp" 

size_t BKDRHash(const char *str)  
{  
    unsigned int seed = 131; // 31 131 1313 13131 131313 
    unsigned int hash = 0;  
    while (*str)  
    {  
        hash = hash * seed + (*str++);  
    }  

    return (hash & 0x7FFFFFFF);  
 }  

size_t SDBMHash(const char *str)  
{  
    register size_t hash = 0;  
    while (size_t ch = (size_t)*str++)  
    {  
        hash = 65599 * hash + ch;  
        //hash = (size_t)ch + (hash << 6) + (hash << 16) - hash; 
    }  
        return hash;  
}  

size_t RSHash(const char *str)  
{  
    register size_t hash = 0;  
    size_t magic = 63689;  
    while (size_t ch = (size_t)*str++)  
    {  
        hash = hash * magic + ch;  
        magic *= 378551;  
    }  
    return hash;  
}  

size_t APHash(const char *str)  
{  
    register size_t hash = 0;  
    size_t ch;  
    for (long i = 0; ch = (size_t)*str++; i++)  
    {  
        if ((i & 1) == 0)  
        {  
            hash ^= ((hash << 7) ^ ch ^ (hash >> 3));  
        }  
        else  
        {  
            hash ^= (~((hash << 11) ^ ch ^ (hash >> 5)));  
        }  
    }  
    return hash;  
}  

size_t JSHash(const char *str)  
{  
    if (!*str)  
        return 0;  

    register size_t hash = 1315423911;  
    while (size_t ch = (size_t)*str++)  
    {  
        hash ^= ((hash << 5) + ch + (hash >> 2));  
    }  
    return hash;  
}  

template<class K>  
struct __HashFunc1  
{  
    size_t operator()(const K& key)  
    {  
        return BKDRHash(key.c_str());  
    }  
};  

template<class K>  
struct __HashFunc2  
{  
    size_t operator()(const K& key)  
    {  
        return SDBMHash(key.c_str());  
    }  
};  

template<class K>  
struct __HashFunc3  
{  
    size_t operator()(const K& key)  
    {  
        return RSHash(key.c_str());  
    }  
};  

template<class K>  
struct __HashFunc4  
{  
    size_t operator()(const K& key)  
    {  
        return APHash(key.c_str());  
    }  
};  

template<class K>  
struct __HashFunc5  
{  
    size_t operator()(const K& key)  
    {  
        return JSHash(key.c_str());  
    }  
};  

template<class K = string,class HashFunc1 = __HashFunc1<K>,class HashFunc2 = __HashFunc2<K>,class HashFunc3 = __HashFunc3<K>,class HashFunc4 = __HashFunc4<K>,class HashFunc5 = __HashFunc5<K>>  
class BloomFilter  
{  
public:  
    BloomFilter(size_t size) 
        : _capacity(_GetNextPrime(size)),_bitmap(_capacity)
    {}  

    void Set(const K& key)  
    {  
        size_t index1 = HashFunc1()(key);  
        size_t index2 = HashFunc2()(key);  
        size_t index3 = HashFunc3()(key);  
        size_t index4 = HashFunc4()(key);  
        size_t index5 = HashFunc5()(key);  

        _bitmap.Set(index1%_capacity);  
        _bitmap.Set(index2%_capacity);  
        _bitmap.Set(index3%_capacity);  
        _bitmap.Set(index4%_capacity);  
        _bitmap.Set(index5%_capacity);  
    }  

    bool IsIn(const K& key)  
    {  
        size_t index1 = HashFunc1()(key);  
        if (!_bitmap.Test(index1%_capacity))  
        {  
            return false;  
        }  
        size_t index2 = HashFunc2()(key);  
        if (!_bitmap.Test(index2%_capacity))  
        {  
            return false;  
        }  
        size_t index3 = HashFunc3()(key);  
        if (!_bitmap.Test(index3%_capacity))  
        {  
            return false;  
        }  
        size_t index4 = HashFunc4()(key);  
        if (!_bitmap.Test(index4%_capacity))  
        {  
            return false;  
        }  
        size_t index5 = HashFunc5()(key);  
        if (!_bitmap.Test(index5%_capacity))  
        {  
            return false;  
        }  

        return true;  
    }  

protected:  

    unsigned long _GetNextPrime(unsigned long num)  
    {  
        const int _PrimeSize = 28;  
        static const unsigned long _PrimeList[_PrimeSize] =  
        {  
            53ul,97ul,193ul,389ul,769ul,1543ul,3079ul,6151ul,12289ul,24593ul,49157ul,98317ul,196613ul,393241ul,786433ul,1572869ul,3145739ul,6291469ul,12582917ul,25165843ul,50331653ul,100663319ul,201326611ul,402653189ul,805306457ul,1610612741ul,3221225473ul,4294967291ul  
        };  
        size_t pos = 0;  
        while (pos < _PrimeSize)  
        {  
            if (_PrimeList[pos] > num)  
            {  
                break;  
            }  
            ++pos;  
        }  
        return _PrimeList[pos];  
    }  

private:  

    size_t _capacity;  
    Bitmap _bitmap;
};  

void Test()  
{  
    BloomFilter<> b1(30);  
    b1.Set("www.baidu.com");  
    b1.Set("www.sina.con");  
    b1.Set("www.taobao.com");  

    cout << b1.IsIn("www.baidu.com") << endl;  
    cout << b1.IsIn("dnsjdasjkdsjakdjas") << endl;  
}

相关文章

【啊哈!算法】算法3:最常用的排序——快速排序       ...
匿名组 这里可能用到几个不同的分组构造。通过括号内围绕的正...
选择排序:从数组的起始位置处开始,把第一个元素与数组中其...
public struct Pqitem { public int priority; ...
在编写正则表达式的时候,经常会向要向正则表达式添加数量型...
来自:http://blog.csdn.net/morewindows/article/details/6...