30天速通C++(十四):位图和布隆过滤器

目录

前言

一. 位图

1.1 位图相关面试题

1.2 位图的设计及实现

1.3 C++库中的位图

二.布隆过滤器

2.1 什么是布隆过滤器

2.2 布隆过滤器器误判率推导

2.3 布隆过滤器代码实现

2.4 布隆过滤器删除问题

三.海量数据处理问题

3.1题目一

3.2 题目二

3.3 题目三


前言


哈喽,各位小伙伴大家好!上次我们讲了二叉搜索树。今天我们来聊一聊位图和布隆过滤器。


一. 位图


1.1 位图相关面试题


给40亿个不重复的无符号整数,没排过序。给⼀个无符号整数,如何快速判断一个数是否在这40亿个数中。(本题为腾讯/百度等公司出过的⼀个面试题)

解题思路1
暴力遍历,时间复杂度O(N),太慢。

解题思路2
排序+二分查找。
时间复杂度消耗 O(N*logN) + O(logN)
深入分析:解题思路2是否可行。


解题思路3
数据是否在给定的整形数据中,结果是在或者不在,刚好是两种状态,那么可以使用一个二进制比特位来代表数据是否存在的信息,如果二进制比特位为1,代表存在,为0代表不存在。那么我们设计一个用位表数据是否存在的数据结构,这个数据结构就叫位图。


1.2 位图的设计及实现


位图本质是⼀个直接定址法的哈希表,每个整型值映射⼀个bit位,位图提供控制这个bit的相关接口。

bit映射关系
实现中需要注意的是,C/C++没有对应位的类型,只能看int/char这样整形类型,我们再通过位运算去控制对应的比特位。比如我们数据存到vector中,相当于每个int值映射对应的32个值,比如第⼀个整形映射0-31对应的位,第⼆个整形映射32-63对应的位,后面的以此类推,那么来了⼀个整形值x,
i = x / 32;j = x % 32;计算出x映射的值在vector的第i个整形数据的第j位。

数据范围
解决给40亿个不重复的无符号整数,查找⼀个数据的问题,我们要给位图开2^32个位,注意不能开40 亿个位,因为映射是按大小映射的,我们要按数据大小范围开空间,范围是是0-2^32-1,所以需要开
2^32个位。然后从文件中依次读取每个set到位图中,然后就可以快速判断⼀个值是否在这40亿个数中
了。

代码实现
这里注意我们映射的数据范围/32可能不整除,所以多开一个int即可。其他都是简单的使用位运算的标记和检测接口。

template<size_t N>
class Bit_Set
{
public:
    Bit_Set()
    {
        bitmap.resize(N / 32 + 1);//多开一个防止不整除
    }
    //将第N个数据标记为1
    void set(size_t n)
    {
        int i = n / 32;
        int j = n  % 32;
        bitmap[i] |= (1 << j);
    }
    //将第N个数据标记为0
    void reset(size_t n)
    {
        int i = n / 32;
        int j = n  % 32;
        bitmap[i] &= (~(1 << j));
    }
    //查看第N个数据是否标记
    bool test(size_t n)
    {
        int i = n / 32;
        int j = n  % 32;
        return bitmap[i] & (1 << j);
    }
private:
    std::vector<int> bitmap;//封装vector
};


这里纠正一下左移的问题。


1.3 C++库中的位图


库里的位图


可以看到核心接口还是set/reset/和test,当然后面还实现了⼀些其他接口,如to_string将位图按位转成01字符串,再包括operator[]等⽀持像数组⼀样控制⼀个位的实现。

但是库的bitset有个小bug就是底层使用的是静态数组。
如果数据范围太大就会越界


1.3位图优缺点
优点:增删查改快,节省空间
缺点:只适用于整形。浮点数字符串这些都不行

1.4位图相关考察题目
给定100亿个整数,设计算法找到只出现一次的整数?
虽然是100亿个数,但是还是按范围开空间,所以还是开2^32个位,跟前面的题目是⼀样的。


⼀个文件有100亿个整数,1G内存,设计算法找到出现次数不超过2次的所有整数
这题目和上题一样只不过需要四种状态,两个bitset也能表示四种状态修改一下即可。

template<size_t N>
class TwoBitSet
{
public:
    void Set(size_t n)
    {
        bool bit1 = b1.test(n);
        bool bit2 = b2.test(n);
        if (!bit1 && !bit2)//00->01
        {
            b2.set(n);
        }
        else if (!bit1&&bit2)//01->10
        {
            b1.set(n);
            b2.reset(n);
        }
        else//10->11 11->11
        {
            b1.set(n);
            b2.set(n);
        }
    }
    int get_count(size_t n)//检测出现次数
    {
        bool bit1 = b1.test(n);
        bool bit2 = b2.test(n);
        if (!bit1 && !bit2)
        {
            return 0;//没出现
        }
        else if (!bit1 && bit2)
        {
            return 1;//出现1次
        }
        else if (bit1 && !bit2)
        {
            return 2;//出现2次
        }
        else
        {
            return 3;//出现3次及以上
        }
    }
private:
    Bit_Set<N> b1;
    Bit_Set<N> b2;//封装
};



这样这两道题都能解决了。


给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件交集?
把数据读出来,分别放到两个位图,依次遍历,同时在两个位图的值都在就是交集。

二.布隆过滤器


2.1 什么是布隆过滤器


有⼀些场景下面,有大量数据需要判断是否存在,而这些数据不是整形,那么位图就不能使用了,使用红黑树/哈希表等内存空间可能不够。这些场景就需要布隆过滤器来解决。

布隆过滤器是由布隆(Burton Howard Bloom)在1970年提出的 ⼀种紧凑型的、比较巧妙的概率型数据结构,特点是高效地插入、和查询,可以用来告诉你 “某样东西⼀定不存在或者可能存在”,它是用多个哈希函数,将⼀个数据映射到位图结构中。此种方式不仅可以提升查询效率,也可以节省大量的内存空间。

哈希映射
布隆过滤器的思路就是把key先映射转成哈希整型值,再映射⼀个位,如果只映射⼀个位的话,冲突率会比较多,所以可以通过多个哈希函数映射多个位,降低冲突率。


判断
布隆过滤器这里跟哈希表不⼀样,它无法解决哈希冲突的,因为他压根就不存储这个值,只标记映射的位。它的思路是尽可能降低哈希冲突。判断⼀个值key在是不准确的,判断⼀个值key不在是准确的。


2.2 布隆过滤器器误判率推导


那我们的哈希函数给几个比较好呢?空间又该开几个bit呢?

布隆过滤器(Bloom Filter)的误判率(False Positive Rate)是其在空间效率与准确性之间权衡的关键指标。以下是详细的数学推导过程:

假设
m:布隆过滤器的bit长度。
n:插入过滤器的元素个数。
k:哈希函数的个数

 
以上两个公式的推导过程尤其复杂,如果感兴趣的可以去看这几篇文章布隆过滤器(Bloom Filter)- 原理、实现和推导_布隆过滤器原理-优快云博客Redis进阶:布隆过滤器(Bloom Filter)及误判率数学推导_布隆过滤器误判率-优快云博客

2.3 布隆过滤器代码实现


首先我们需要几个哈希函数,这里我们就简单的以字符串的哈希函数为例。
各种字符串Hash函数-clq-博客
这里有几个比较好的字符串哈希函数。

struct HashFuncBKDR
{

    //算法累乘因⼦为31。
    size_t operator()(const std::string& s)
    {
        size_t hash = 0;
        for (auto ch : s)
        {
            hash *= 31;
            hash += ch;
        }
        return hash;
    }
};
struct HashFuncAP
{
    // 由Arash Partow发明的⼀种hash算法。 
    size_t operator()(const std::string& s)
    {
        size_t hash = 0;
        for (size_t i = 0; i < s.size(); i++)
        {
            if ((i & 1) == 0) // 偶数位字符
            {
                hash ^= ((hash << 7) ^ (s[i]) ^ (hash >> 3));
            }
            else // 奇数位字符
            {
                hash ^= (~((hash << 11) ^ (s[i]) ^ (hash >>
                    5)));
            }
        }
        return hash;
    }
};
struct HashFuncDJB
{
    // 由Daniel J. Bernstein教授发明的⼀种hash算法。 
    size_t operator()(const std::string& s)
    {
        size_t hash = 5381;
        for (auto ch : s)
        {
            hash = hash * 33 ^ ch;
        }
        return hash;
    }
};
template<size_t N>
class Bit_Set
{
public:
    Bit_Set()
    {
        bitmap.resize(N / 32 + 1);
    }
    //将第N个数据标记为1
    void set(size_t n)
    {
        int i = n / 32;
        int j = n % 32;
        bitmap[i] |= (1 << j);
    }
    //将第N个数据标记为0
    void reset(size_t n)
    {
        int i = n / 32;
        int j = n % 32;
        bitmap[i] &= (~(1 << j));
    }
    //查看第N个数据是否标记
    bool test(size_t n)
    {
        int i = n / 32;
        int j = n % 32;
        return bitmap[i] & (1 << j);
    }
private:
    std::vector<int> bitmap;
};
template<size_t N, 
         size_t X=6,
         class K=string,
         class Hash1= HashFuncBKDR,
         class Hash2= HashFuncAP,
         class Hash3= HashFuncDJB>
class Bloom
{
public:
    void Set(const K& key)
    {
        size_t b1 = Hash1()(key) % M;
        size_t b2 = Hash2()(key) % M;
        size_t b3 = Hash3()(key) % M;//三个哈希函数映射的值
        b.set(b1);
        b.set(b2);
        b.set(b3);//标记
    }
    bool Test(const K& key)
    {
        size_t b1 = Hash1()(key) % M;
        size_t b2 = Hash2()(key) % M;
        size_t b3 = Hash3()(key) % M;//三个哈希函数映射的值
        if (!b.test(b1))//只要有一个没标记就返回0说明不在
        {
            return 0;
        }
        if (!b.test(b2))//只要有一个没标记就返回0说明不在
        {
            return 0;
        }
        if (!b.test(b3))//只要有一个没标记就返回0说明不在
        {
            return 0;
        }
        return true;//可能存在误判
    }
    // 获取公式计算出的误判率
    double getFalseProbability()
    {
        double p = pow((1.0 - pow(2.71, -3.0 / X)), 3.0);
        return p;
    }
private:
    static const size_t M = X * N;
    Bit_Set<M> b;
};


测试
 

void Test()
{
    qcj::Bloom<10> b;
    b.Set("孙悟空");
    b.Set("猪八戒");
    b.Set("唐僧");
    cout << b.Test("孙悟空") << endl;;
    cout << b.Test("猪八戒") << endl;;
    cout << b.Test("唐僧") << endl;
    cout << b.Test("沙僧") << endl;;
    cout << b.Test("猪八戒1") << endl;;
}

void TestBloomFilter2()
{
    srand(time(0));
    const size_t N = 100000;
    /*qcj::Bloom<N> bf;*/
    qcj::Bloom<N, 3> bf;
    /*qcj::Bloom<N, 10> bf;*/
    std::vector<std::string> v1;
    std::string url = "https://www.cnblogs.com/-clq/archive/2012/05/31/2528153.html";
    //std::string url = "https://www.baidu.com/s?ie=utf8&f=8&rsv_bp=1&rsv_idx=1&tn=65081411_1_oem_dg&wd=ln2&fenlei=256&rsv_pq=0x8d9962630072789f & rsv_t = ceda1rulSdBxDLjBdX4484KaopD % 2BzBFgV1uZn4271RV0PonRFJm0i5xAJ % 2FDo & rqlang = en & rsv_enter = 1 & rsv_dl = ib & rsv_sug3 = 3 & rsv_sug1 = 2 & rsv_sug7 = 100 & rsv_sug2 =0 & rsv_btype = i & inputT = 330 & rsv_sug4 = 2535";
    //std::string url = "猪⼋戒";
    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是相似字符串集(前缀⼀样),但是后缀不⼀样
    v1.clear();
    for (size_t i = 0; i < N; ++i)
    {
        std::string urlstr = url;
        urlstr += std::to_string(9999999 + i);
        v1.push_back(urlstr);
    }
    size_t n2 = 0;
    for (auto& str : v1)
    {
        if (bf.Test(str)) // 误判
        {
            ++n2;
        }
    }
    cout << "相似字符串误判率:" << (double)n2 / (double)N << endl;
    // 不相似字符串集 前缀后缀都不⼀样
    v1.clear();
    for (size_t i = 0; i < N; ++i)
    {
        //string url = "zhihu.com";
        string url = "孙悟空";
        url += std::to_string(i + rand());
        v1.push_back(url);
    }
    size_t n3 = 0;
    for (auto& str : v1)
    {
        if (bf.Test(str))
        {
            ++n3;
        }
    }
    cout << "不相似字符串误判率:" << (double)n3 / (double)N << endl;
    cout << "公式计算出的误判率:" << bf.getFalseProbability() << endl;
}



这里我们用相似字符串,不相似字符串,和公式计算的误判率对比。

可以发现这里我们公式算的还是非常的准的。

2.4 布隆过滤器删除问题


布隆过滤器默认是不支持删除的,因为比如"猪八戒“和”孙悟空“都映射在布隆过滤器中,他们映射的位有⼀个位是共同映射的(冲突的),如果我们把孙悟空删掉,那么再去查找“猪八戒”会查找不到,因为那么“猪八戒”间接被删掉了。

所以删除一个值可能会影响另外一个值。

解决方案一
可以考虑计数标记的方式,⼀个位置用多个位标记,记录映射这个位的计数值,删除时,仅仅减减计数,那么就可以某种程度⽀持删除。但是这个方案也有缺陷,如果⼀个值不在布隆过滤器中,我们去删除,减减了映射位的计数,那么会影响已存在的值,也就是说,⼀个确定存在的值,可能会变成不存在,这里就很坑。


解决方案二
当然也有⼈提出,我们可以考虑计数方式支持删除,但是定期重建⼀
下布隆过滤器,这样也是⼀种思路。

所以严格来说布隆过滤器不支持删除,因为会进一步导致误判。但是可以结合实际场景和需求进行挑选删除的解决方案。

2.5 布隆过滤器的应用
首先我们分析⼀下布隆过滤器的优缺点:
优点:效率高,节省空间,相比位图,可以适⽤于各种类型的标记过滤
缺点:存在误判(在是不准确的,不在是准确的),不好支持删除

布隆过滤器在实际中的⼀些应用:

爬虫系统中URL去重:
在爬虫系统中,为了避免重复爬取相同的URL,可以使用布隆过滤器来进行URL去重。爬取到的URL可以通过布隆过滤器进行判断,已经存在的URL则可以直接忽略,避免重复的网络请求和数据处理。

垃圾邮件过滤:
在垃圾邮件过滤系统中,布隆过滤器可以用来判断邮件是否是垃圾邮件。系统可以将已知的垃圾邮件的特征信息存储在布隆过滤器中,当新的邮件到达时,可以通过布隆过滤器快速判断是否为垃圾邮件,从而提高过滤的效率。

预防缓存穿透
在分布式缓存系统中,布隆过滤器可以用来解决缓存穿透的问题。缓存穿透是指恶意用户请求⼀个不存在的数据,导致请求直接访问数据库,造成数据库压力过大。布隆过滤器可以先判断请求的数据是否存在于布隆过滤器中,如果不存在,直接返回不存在,避免对数据库的无效查询。


对数据库查询提效
在数据库中,布隆过滤器可以⽤来加速查询操作。例如:⼀个app要快速判断⼀个电话号码是否注册过,可以使用布隆过滤器来判断⼀个用户电话号码是否存在于表中,如果不存在,可以直接返回不存在,避免对数据库进行无用的查询操作。如果在,再去数据库查询进行二次确认。


三.海量数据处理问题


3.1题目一


100亿个整数里面求最大的前100个。
这里我们建一个100的小堆。在把100亿个整数比堆顶元素大的进堆即可。
剩下的100堆元素就是最大的100个数。

这个题目其实是数据结构中的堆更好解决,因为堆可以选择建大堆或者建小堆,对于本道题目,我们建小堆,因为小根堆的堆顶是当前堆中的最小值。我们维护一个 ​大小为100的小根堆,堆顶就是当前第100大的数。

  • 对于新元素:
    • 如果 ​比堆顶大,说明它可能是前100大的数,替换堆顶并调整堆。
    • 如果 ​比堆顶小或等于,直接丢弃(因为不可能进前100)。

算法步骤

  1. 初始化堆​:

    • 取前100个数,建立一个小根堆。
  2. 遍历剩余数据​:

    • 对于每个新元素 x
      • 如果 x > 堆顶元素
        • 替换堆顶(相当于淘汰当前最小的候选)。
        • 调整堆,使其仍满足小根堆性质。
      • 否则跳过。
  3. 最终结果​:堆中的100个数就是最大的前100个。


3.2 题目二


给两个文件,分别有100亿个query(字符串),我们只有1G内存,如何找到两个文件交集
分析:假设平均每个query字符串50byte,100亿个query就是5000亿byte,约等于500G(1G 约等于10亿多Byte)

哈希表/红黑树等数据结构肯定是无能为力的。
解决方案1
这个首先可以用布隆过滤器解决,⼀个文件中的query放进布隆过滤器,另⼀个文件依次查找,在的就是交集,问题就是找到交集不够准确,因为在的值可能因为哈希冲突导致误判的,但是交集一定被找到了。
所以这种方法还是难以满足我们的需求。
解决方案2
• 哈希切分,首先内存的访问速度远大于硬盘,大文件放到内存搞不定,那么我们可以考虑切分为小文件,再放进内存处理。
• 但是不要平均切分,因为平均切分以后,每个小文件都需要依次暴力处理,效率还是太低了。
• 可以利用哈希切分,依次读取文件中query,i = HashFunc(query)%N,N为准备切分多少分小文件,N取决于切成多少份,内存能放下,query放进第i号小文件,这样A和B中相同的query算出的hash值i是⼀样的,相同的query就进入的编号相同的小文件就可以编号相同的我文件直接找交集,不用交叉找,效率就提升了。
• 本质是相同的query在哈希切分过程中,一定进入的同一个小问件Ai和Bi,不可能出现A中的的query进入Ai,但是B中的相同query进入了和Bj的情况,所以对Ai和Bi进行求交集即可,不需要Ai和Bj求交集。(本段表述中i和j是不同的整数)
• 哈希切分的问题就是每个小文件不是均匀切分的,可能会导致某个小文件很大内存放不下。我们细细分析⼀下某个小文件很大有两种情况:
1.这个小文件中大部分是同⼀个query。
2.这个小文件是有很多的不同query构成,本质是这些query冲突了。针对情况1,其实放到内存的set中是可以放下的,因为set是去重的。针对情况2,需要换个哈希函数继续⼆次哈希切分。所以本体我们遇到大于1G小文件,可以继续读到set中找交集,若set insert时抛出了异常(set插⼊数据抛异常只可能是申请内存失败了,不会有其他情况),那么就说明内存放不下是情况2,换个哈希函进行⼆次哈希切分后再对应找交集。

3.3 题目三


给⼀个超过100G大小的log file, log中存着ip地址, 设计算法找到出现次数最
多的ip地址?查找出现次数前10的ip地址
本题的思路跟上题完全类似,依次读取文件A中query,i = HashFunc(query)%500,query放进Ai号小文件,然后依次⽤map<string, int>对每个Ai小文件统计ip次数,同时求出现次数最多的ip或者topkip。本质是相同的ip在哈希切分过程中,⼀定进⼊的同⼀个小文件Ai,不可能出现同一个ip进入Ai和Aj的情况,所以对Ai进行统计次数就是准确的ip次数。

<think>好的,用户想了解C++位图布隆过滤器的相关知识及总结,我需要结合提供的引用资料来组织回答。首先,我应该先明确位图布隆过滤器的基本概念,然后讨论它们的实现应用,最后总结它们的优缺点应用场景。 首先看位图部分。根据引用[3],位图用于处理海量数据的存在性判断,比如40亿个整数去重。位图过每个bit位表示一个数据是否存在,节省空间。需要提到位图数据结构设计,比如用vector<bool>或直接操作比特位。实现部分可能需要给出C++的代码示例,比如设置某位为1,清除为0,检查存在性。应用方面包括快查找、去重、排序等,引用[2][3]都有提到这些应用。 然后是布隆过滤器。根据引用[4],布隆过滤器是概率型数据结构,用于判断元素可能存在或一定不存在。它使用多个哈希函数映射到不同的位,引用[3][4]都提到了这一点。实现部分需要说明插入查找的过程,以及为什么不能删除,或者如何支持删除(可能需要引用计数)。应用方面,引用[5]提到了缓存穿透的解决方案,防止数据库被无效查询压垮。此外,分布式系统爬虫中的URL去重也是常见应用。 需要注意布隆过滤器的优缺点,比如空间效率高但存在误判率,无法删除元素等。引用[4][5]都有相关描述。可能需要比较位图布隆过滤器的适用场景,比如位图适合精确判断且数据范围小的情况,而布隆过滤器适合大数据量且允许一定误判的情况。 代码实现部分,位图可以用bitset或手动操作位,布隆过滤器则需要多个哈希函数位图的组合。需要确保示例代码正确,比如展示如何设置位,检查存在性,以及布隆过滤器的插入查询步骤。 最后总结部分,需要将位图布隆过滤器的核心点对比,强调它们在海量数据处理中的重要性,并结合引用中的内容,如引用[1]提到的结合位图布隆过滤器的方案,可能可以提到这种混合结构在优化空间查询度上的优势。 现在需要检查是否有遗漏的引用内容,比如引用[1]提到结合位图布隆过滤器的优点,这可能是在实际应用中的优化策略,需要在总结部分提到。同时,用户要求生成相关问题,需要根据内容设计相关问题,比如应用场景、实现细节、优缺点比较等。 需要注意回答的结构,先讲位图,再讲布隆过滤器,每个部分包括概念、实现、应用,最后总结对比。确保引用标识正确添加,例如在提到布隆过滤器概念时加上[^4],在缓存穿透时引用[^5]等。</think>### C++位图布隆过滤器总结 #### 一、位图(Bitmap) **1. 概念与实现** 位图过每个比特位(bit)表示一个数据的存在性(0/1),适用于海量数据的快存在性判断[^3]。例如,40亿个整数(约16GB)用位图仅需约500MB内存。 **C++实现示例**: ```cpp class Bitmap { private: std::vector<uint32_t> bits; // 使用uint32_t数组存储位 public: Bitmap(size_t range) : bits((range >> 5) + 1, 0) {} // 计算需要多少32位块 void set(size_t x) { size_t index = x >> 5; // 确定数组下标 size_t offset = x % 32; // 确定比特位偏移 bits[index] |= (1 << offset); } bool test(size_t x) { size_t index = x >> 5; size_t offset = x % 32; return (bits[index] & (1 << offset)) != 0; } }; ``` **2. 应用场景** - **数据去重**:如统计40亿整数中不重复的数字。 - **快查询**:判断IP是否在黑名单中。 - **排序**:对有限范围的整数进行非比较排序[^2]。 --- #### 二、布隆过滤器(Bloom Filter) **1. 概念与实现** 布隆过滤器过$k$个哈希函数将元素映射到多个位,若所有对应位为1则“可能存在”,否则“一定不存在”[^4]。牺牲一定准确性换取极高的空间效率。 **C++实现核心逻辑**: ```cpp class BloomFilter { private: Bitmap bitmap; size_t k; // 哈希函数数量 public: BloomFilter(size_t size, size_t k) : bitmap(size), k(k) {} void add(const std::string& key) { for (size_t i = 0; i < k; ++i) { size_t hash = std::hash<std::string>{}(key + std::to_string(i)); bitmap.set(hash % bitmap.size()); } } bool contains(const std::string& key) { for (size_t i = 0; i < k; ++i) { size_t hash = std::hash<std::string>{}(key + std::to_string(i)); if (!bitmap.test(hash % bitmap.size())) return false; } return true; // 可能存在(有一定误判率) } }; ``` **2. 应用场景** - **缓存穿透防护**:拦截不存在的数据请求,保护数据库。 - **分布式系统**:减少节点间冗余数据传输。 - **爬虫URL去重**:避免重复抓取已处理的页面[^3]。 **3. 优缺点** - **优点**:空间效率高,查询时间$O(k)$,适合海量数据[^4]。 - **缺点**:误判率随元素增加而上升,且不支持删除(除非引入计数布隆过滤器)。 --- #### 三、对比与总结 | **特性** | **位图** | **布隆过滤器** | |------------------|------------------------------|------------------------------| | **数据范围** | 适用于整数且范围较小 | 支持任意数据类型 | | **误判率** | 无 | 有(可调整哈希函数数量优化) | | **删除支持** | 直接修改位即可 | 需额外结构(如计数位图) | | **典型场景** | 精确存在性判断 | 允许误判的存在性预筛 | **混合优化方案**:结合位图布隆过滤器,例如用位图处理高频数据,布隆过滤器处理低频数据,提升整体性能[^1]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值