0
点赞
收藏
分享

微信扫一扫

布隆过滤器(5千字长文详解)

布隆过滤器

位图

位图的概念

在介绍位图之前我们先看一个题目

给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在 这40亿个数中。

位图的实现

首先我们要解决如何开辟和映射的问题——==因为C++是不支持按bit位开辟空间的!

image-20230503153734074

==也可以按照int类型 除8与模8改成除32与模32==

成员变量

#pragma once
#include <iostream>
#include <vector>
namespace MySTL
{
       template<size_t N>//使用非类型模版参数
       class bitset
       {
           private:
           std::vector<char> _bit;
       };
}

成员函数

构造函数
#pragma once
#include <iostream>
#include <vector>
namespace MySTL
{
       template<size_t N>//使用非类型模版参数
       class bitset
       {
           public:
           bitset()
           {
               //我们开辟的空间不是N,而是N/8+1
               //因为我们要的是N个bit,而一个字节是8个bit
               //为什么要+1呢因为防止不是8的倍数,例如20,20/8=2,但是20个bit需要3个字节
               //所以我们要+1
               _bit.resize(N/8+1,0);
               //如果想使用位移要记得加括号,因为<<的优先级比+低
               //_bit.resize((N>>3)+1,0);
           }
           private:
           std::vector<char> _bit;
       };
} 	
set

set就是将bit位置为1

#include <vector>
namespace MySTL
{
    template<size_t N>//使用非类型模版参数
    class bitset
    {
        public:
        void set(size_t x)//set就是将bit位置为1
        {
            size_t index = x/8;//确定在哪个字节
            size_t pos = x % 8;//确定在字节中的哪个bit

            //将对应的BIT位置为1
            //可以使用或进行运算!
            //因为或要两个都为0才为0,其他情况都为1
            //我们只要有一个除了与这个BIT对应的位置为1,其他位置都为0的数
            //我们就可以通过或将这个BIT位置为1,其他位置不变
            //无论这个BIT原来是0还是1,都会变成1
            _bit[index] |= (1<<pos);
            //(1<<pos),将1左移pos位,就是将1放在pos位置上
            //因为二级制位是左高右低,所以要向左移动pos位
            //这里和大小端没有关系,因为我们使用的是char,char是一个字节
        }
        private:
        std::vector<char> _bit;
    };
}

image-20230503162916220

reset
#pragma once
#include <iostream>
#include <vector>
namespace MySTL
{
       template<size_t N>//使用非类型模版参数
       class bitset
       {
           public:
           void reset(size_t x)//reset就是将bit位置为0
           {
               size_t index = x/8;
               size_t pos = x % 8;

               //reset是将该bit位置为0.无论原先是0还是1
               //这时候我们可以使用与运算
               //因为与运算要两个都为1才为1,其他情况都为0
               //我们可以使用一个除了这个BIT位置为0,其他位置都为1的数
               //例如pos的值为 2,先将1左移2位,就是将1放在第二位上
               //然后进行取反,就是将这个数的所有位取反
               //然后再进行与运算,就可以将这个BIT位置为0,其他位置不变
               _bit[index] &= ~(1<<pos);
           }
           private:
           std::vector<char> _bit;
       };
}

image-20230504092406223

test
#pragma once
#include <iostream>
#include <vector>
namespace MySTL
{
       template<size_t N>//使用非类型模版参数
       class bitset
       {
           public:
           bool test(size_t x)//检查这个bit是否为1
           {
               //这个也很简单,我们只要有一个除了这个BIT位置为1,其他位置都为0的数
               //然后进行与运算,就可以得到这个BIT的值
               //如果为0,就是false,如果不为,就是true
               size_t index = x/8;
               size_t pos = x % 8;
               return _bit[index] & (1<<pos);
           }
           private:
           std::vector<char> _bit;
       };
}

位图总结

==位图用来判断大量的整形数据存不存在的时候效率是非常高的!==

==但是位图不是万能的!因为位图只能用来判断整形!==

而库里面也为我们提供了这个库

image-20230504155239606

==不过库里面的接口比我们实现的更多,我们主要实现了的它的几个关节接口的实现!==

位图应用

==位图一般都是用来处理大量的数据==

例1:给定100亿个整数,设计算法找到只出现一次的整数?

位图优缺点

位图的优点

  1. 节省空间
  2. 效率高(快)

位图的缺点

  1. 要求范围相对集中,范围特别分散,空间消耗就提升(例如有100个值,一个最小是1,最大是42亿,那么消耗就很大了!)
  2. 只能针对整形!

布隆过滤器的概念

为什么会有布隆过滤器这种东西呢?

==布隆过滤器其实是在位图的基础上进一步的产物==

从位图我们可以看出来,判断一个值是否存在其实不用那么麻烦,如果是使用哈希表或者红黑树,我们还要把值都存起来!然后再找!这样子其实消耗很大!如果数据量太大了!那么红黑树和哈希表就不够用了!——所以有了位图,==只用一个标记位来标记一个值在或者不再!==

==上面我们知道位图的缺点之一就是只能针对整形!那么我们有没有办法字符串进行映射呢?——答案是使用哈希函数(hashFunc(str))将字符串转换整形!然后通过这个整形来映射!不止是字符串!我们可以通过不同的哈希函数!将不同的类型转换为整形!==

==布隆过滤器就是上面方法的一种优化!==

既然会出现误判!那么我们有什么办法来减少误判呢?

image-20230505180439903

==但是这种方法是不能避免误判的!只能减小误判的概率!因为有可能所有的位置都跟别的冲突了!==——这种方法就叫布隆过滤器!

==布隆过滤器的改进==

映射多个位置,降低误判率!(但是不是映射位置越多越好!应该这样子会造成很大空间浪费!)

布隆过滤器的应用

1 )可以适用于一些不一定准确的场景——注册时候的昵称判重!

布隆过滤器的实现

#pragma once
#include <iostream>
#include <bitset>
#include <string>
using namespace std;
namespace MySTL
{
    template<class T>
    struct BKDRHash
    {
        size_t operator()(const T &str)
        {
            size_t hash = 0;
            for(auto chi :str)
            {
                size_t ch = (size_t)chi;
                hash = hash * 131 + ch;
            }
            return hash;
        }
    };
    template<class T>
    struct SDBMHash
    {
        size_t operator()(const T &str)
        {
            size_t hash = 0;
            for(auto chi :str)
            {
                size_t ch = (size_t)chi;
                hash = 65599 * hash + ch;
                //hash = (size_t)ch + (hash << 6) + (hash << 16) - hash;
            }
            return hash;
        }
    };


    template<class T>
    struct RSHash
    {
        size_t operator()(const T &str)
        {
            size_t hash = 0;
            size_t magic = 63689;
            for(auto chi :str)
            {
                size_t ch = (size_t)chi;
                hash = hash * magic + ch;
                magic *= 378551;
            }
            return hash;
        }
    };

    template<size_t N,//需要存储的最大数据!
            size_t X= 5,//平均存储一个值,开辟X位的空间!
            class K = std::string,
            class HashFunc1 = BKDRHash<K>,
            class HashFunc2 = SDBMHash<K>,
            class HashFunc3 = RSHash<K>>//哈希函数个数和玻璃长度有关系!
    class BloomFilter
    {
    public:
        void set(const K& key)
        {
            size_t hash1 = HashFunc1()(key) % (X*N);//要加上()因为 % 优先级高于*
            size_t hash2 = HashFunc2()(key) % (X*N);
            size_t hash3 = HashFunc3()(key) % (X*N);
            _bs.set(hash1);
            _bs.set(hash2);
            _bs.set(hash3);
        }
        bool test(const K& key)
        {
            size_t hash1 = HashFunc1()(key) % (X*N);
            if(!_bs.test(hash1))
                return false;
            size_t hash2 = HashFunc2()(key) % (X*N);
            if(!_bs.test(hash2))
                return false;
            size_t hash3 = HashFunc3()(key) % (X*N);
            if(!_bs.test(hash3))
                return false;

            //前面判断不在是不准确的!
            return true;//可能存在误判!映射的几个位置都冲突了!
        }
    private:
        std::bitset<N*X> _bs;
    };
}

关于要几个哈希函数,和布隆过滤器的长度有关!

详解布隆过滤器的原理,使用场景和注意事项 - 知乎 (zhihu.com)关于想在这个方面更加的深入可以看这篇文章

image-20230506145656648

==过滤器是用来减低误判的!不是用来避免误判的!==

==这个不支持reset!因为一个位置可能会被多个值给映射!如果reset掉可能就会影响其他的查找!==

image-20230506171219407

如果我们把find给reset了!那么我们就会影响Insert!

测试
#pragma once
#include <iostream>
#include <bitset>
#include <string>
namespace MySTL
{
    void test_bloomfilter()
    {
        srand(time(0));
        const size_t N = 100000;
        BloomFilter<N> bf;

        std::vector<std::string> v1;
        std::string url = "https://www.cnblogs.com/-clq/archive/2012/05/31/2528153.html";

        for (size_t i = 0; i < N; ++i)
        {
            v1.push_back(url + std::to_string(i));
        }

        for (auto& str : v1)
        {
            bf.set(str);
        }

        // v2跟v1是相似字符串集,但是不一样
        std::vector<std::string> v2;
        for (size_t i = 0; i < N; ++i)
        {
            std::string url = "https://www.cnblogs.com/-clq/archive/2012/05/31/2528153.html";
            url += std::to_string(999999 + i);
            v2.push_back(url);
        }

        size_t n2 = 0;
        for (auto& str : v2)
        {
            if (bf.test(str))
            {
                ++n2;
            }
        }
        cout << "相似字符串误判率:" << (double)n2 / (double)N << endl;

        // 不相似字符串集
        std::vector<std::string> v3;
        for (size_t i = 0; i < N; ++i)
        {
            string url = "zhihu.com";
            url += std::to_string(i+rand());
            v3.push_back(url);
        }

        size_t n3 = 0;
        for (auto& str : v3)
        {
            if (bf.test(str))
            {
                ++n3;
            }
        }
        cout << "不相似字符串误判率:" << (double)n3 / (double)N << endl;
    }
}

image-20230506164808095

==我们可以通过增大X与增加哈希函数个数用来减少误判率!==

举报

相关推荐

0 条评论