哈希拓展——布隆过滤器

哈希表的另外一个应用是布隆过滤器。

1.什么是布隆过滤器


关于布隆过滤器,我们需要讲很多关于它的东西,首先我们可以知道这个东西是个叫做布隆的人发现的,它一般用于解决网络爬虫重复问题,还有比如防垃圾邮件或垃圾网页的实现,都可以利用它。

布隆过滤器也是针对的是哈希冲突,可以通过一个Hash函数将一个元素映射成一个位阵列(Bit Array)中的一个点。这样一来,我们只要看看这个点是不是 1 就知道可以集合中有没有它了。这就是布隆过滤器的基本思想。

通俗点说也就是本来是一个位表示存在还是不存在,为了方便解决冲突问题,我们在这里采用多个位来表示一个数的状态,在这有个特别重要的特点:布隆中不存在是确定的,存在是不一定的,因为多个位置表示一个数,如果这些位当中有一位是0,那么这个数一定是不存在的,但是如果这个数对应的位置都是1,不一定代表他存在,因为也可能是其它数的位是1,影响了它。

2.布隆过滤器的优缺点


布隆的优点:是空间效率和查询时间都远远超过一般的算法,布隆过滤器存储空间和插入 / 查询时间都是常数O(1)。另外, 散列函数相互之间没有关系,方便由硬件并行实现。布隆过滤器不需要存储元素本身,因为它所存储的是它数据的状态,在某些对保密要求非常严格的场合有优势。

布隆过滤器的缺点:误算率是其中之一。随着存入的元素数量增加,误算率随之增加。但是如果元素数量太少,则使用散列表足矣。
(误判补救方法是:再建立一个小的白名单,存储那些可能被误判的信息。)

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

总结


在计算机科学中,我们常常会碰到时间换空间或者空间换时间的情况,即为了达到某一个方面的最优而牺牲另一个方面。Bloom Filter在时间空间这两个因素之外又引入了另一个因素:错误率。在使用Bloom Filter判断一个元素是否属于某个集合时,会有一定的错误率。也就是说,有可能把不属于这个集合的元素误认为属于这个集合(False Positive),但不会把属于这个集合的元素误认为不属于这个集合(False Negative)。在增加了错误率这个因素之后,Bloom Filter通过允许少量的错误来节省大量的存储空间。

自从Burton Bloom在70年代提出Bloom Filter之后,Bloom Filter就被广泛用于拼写检查和数据库系统中。

代码实现:

#define _CRT_SECURE_NO_WARNINGS 1

#pragma once
#include<iostream>
#include<cstdlib>
#include<cassert>
#include"BitMap.hpp"
using namespace std;

template <typename K>
struct _Funcpos1
{
    size_t BKDRHash(const char *str)
    {
        register size_t hash = 0;
        while (size_t ch = (size_t)*str++)
        {
            hash = hash * 131 + ch;   // 也可以乘以31、131、1313、13131、131313..  
            // 有人说将乘法分解为位运算及加减法可以提高效率,如将上式表达为:hash = hash << 7 + hash << 1 + hash + ch;  
            // 但其实在Intel平台上,CPU内部对二者的处理效率都是差不多的,  
            // 我分别进行了100亿次的上述两种运算,发现二者时间差距基本为0(如果是Debug版,分解成位运算后的耗时还要高1/3);  
            // 在ARM这类RISC系统上没有测试过,由于ARM内部使用Booth's Algorithm来模拟32位整数乘法运算,它的效率与乘数有关:  
            // 当乘数8-31位都为1或0时,需要1个时钟周期  
            // 当乘数16-31位都为1或0时,需要2个时钟周期  
            // 当乘数24-31位都为1或0时,需要3个时钟周期  
            // 否则,需要4个时钟周期  
            // 因此,虽然我没有实际测试,但是我依然认为二者效率上差别不大          
        }
        return hash;
    }

    size_t operator()(const string& key)
    {
        return BKDRHash(key.c_str());
    }
};
template<typename K>
struct _Funcpos2
{
    size_t SDBMHash(const char *str)
    {
        register size_t hash = 0;
        while (size_t ch = (size_t)*str++)
        {
            hash = 65599 * hash + ch;
        }
        return hash;
    }
    size_t operator()(const string& key)
    {
        return SDBMHash(key.c_str());
    }
};
template<typename K>
struct _Funcpos3
{
    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 operator()(const string& key)
    {
        return RSHash(key.c_str());
    }
};
template<typename K>
struct _Funcpos4
{
    size_t RSHash(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 operator()(const string& key)
    {
        return RSHash(key.c_str());
    }
};

template<typename K>
struct _Funcpos5
{
    size_t RSHash(const char *str)
    {
        if (!*str)        // 这是由本人添加,以保证空字符串返回哈希值0  
            return 0;
        register size_t hash = 1315423911;
        while (size_t ch = (size_t)*str++)
        {
            hash ^= ((hash << 5) + ch + (hash >> 2));
        }
        return hash;
    }
    size_t operator()(const string& key)
    {
        return RSHash(key.c_str());
    }
};

template<typename K = string, 
    typename Funcpos1 = _Funcpos1<K>,
    typename Funcpos2 = _Funcpos2<K>,
    typename Funcpos3 = _Funcpos3<K>,
    typename Funcpos4 = _Funcpos4<K>,
    typename Funcpos5 = _Funcpos5<K> >
class BloomFilter
{
public:
    //构造函数
    BloomFilter(const size_t range)
        : _size(range)
        , _bl(range)
    {   
    }
    //布隆过滤器进行设置,根据原理,对通过字符串函数所计算的K个位进行设置状态。
     void Set(const K& key)
    {
        size_t index1 = Funcpos1()(key.c_str())%_size;
        size_t index2 = Funcpos2()(key.c_str())%_size;
        size_t index3 = Funcpos3()(key.c_str())%_size;
        size_t index4 = Funcpos4()(key.c_str())%_size;
        size_t index5 = Funcpos5()(key.c_str())%_size;

        _bl.Set(index1);
        cout << index1 << endl;

        _bl.Set(index2);
        cout << index2 << endl;

        _bl.Set(index3);
        cout << index3 << endl;

        _bl.Set(index4);
        cout << index4 << endl;

        _bl.Set(index5);
        cout << index5 << endl<<endl;

    }


     bool Test(const K& key)
     {
         size_t index1 = Funcpos1()(key.c_str() )% _size;
         if (_bl.test(index1) == 0)
         {
             return false;
         }
         size_t index2 = Funcpos2()(key.c_str()) % _size;
         if (_bl.test(index2) == 0)
         {
             return false;
         }
         size_t index3 = Funcpos3()(key.c_str()) % _size;
         if (_bl.test(index3) == 0)
         {
             return false;
         }
         size_t index4 = Funcpos4()(key.c_str() )% _size;
         if (_bl.test(index4) == 0)
         {
             return false;
         }
         size_t index5 = Funcpos5()(key.c_str() )% _size;
         if (_bl.test(index5) == 0)
         {
             return false;
         }
         return true;
     }

protected:
    BitMap _bl;
    size_t _size;
};

关于位图的实现参考我上一篇关于位图的博客。

代码已上传github:https://github.com/wsy081414/data-structure/tree/master/%E5%93%88%E5%B8%8C%E8%A1%A8

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值