C++:位图与布隆过滤器

位图概念

  1. 【面试题】

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

三种解法如下:

  1. 遍历,时间复杂度O(N)。
  2. 排序 ( O ( N l o g N ) ) (O(NlogN)) (O(NlogN)),利用二分查找: l o g N logN logN有足够的内存去排序
  3. 位图解决(推荐解法)

数据是否在给定的整形数据中,结果是在或者不在,刚好是两种状态,那么可以使用一个二进制比特位来代表数据是否存在的信息,如果二进制比特位为1,代表存在,为0代表不存在。 比如:
在这里插入图片描述

  1. 位图概念

所谓位图,就是用每一位来存放某种状态,适用于海量数据,数据无重复的场景。通常是用来判断某个数据存不存在的。

位图的实现
class bitset
{
public:
    bitset(size_t bitCount) : 
        _bit((bitCount >> 5) + 1), 
        _bitCount(bitCount)
    {}

    // 将which比特位置1 
    void set(size_t which) {
        if (which > _bitCount)
            return;
        size_t index = (which >> 5);
        size_t pos = which % 32;
        _bit[index] |= (1 << pos);
    }

    // 将which比特位置0
    void reset(size_t which) {
        if (which > _bitCount)
            return;
        size_t index = (which >> 5);
        size_t pos = which % 32;
        _bit[index] &= ~(1 << pos);
    }

    // 检测位图中which是否为1 
    bool test(size_t which) {
        if (which > _bitCount)
            return false;
        size_t index = (which >> 5);
        size_t pos = which % 32;
        
        return _bit[index] & (1 << pos);
    }

    // 获取位图中比特位的总个数
    size_t size()const 
    { 
        return _bitCount; 
    } 
    
    // 位图中含比特为1的个数
    size_t count()
    {
        // \数字  表实对应的数字(八进制省略0)
        const char* pCount = "\0\1\1\2\1\2\2\3\1\2\2\3\2\3\3\4";
                            // 0 1 2 3 4 5 6 7 8 9 a b c d e f(十六进制中含1的个数)

        size_t size = _bit.size();
        size_t count = 0;
        size_t i;
        for (i = 0; i < size; i++)
        {
            int value = _bit[i];
            int j;

            // sizeof(_bit[0]) 是为了防止某些系统的int大小不固定
            for (j = 0; j < sizeof(_bit[0]); j++, value >>= 8)
            {
                // 利用c进行截断(value32位,取低8位)
                char c = value;

                // 先看C的低4位,再看高4位(一次统计一个字节)
                count += pCount[c & 0x0f];
                c >> 4;
                count += pCount[c & 0x0f];
            }
        }
        return count;
    }
private:
    vector<int> _bit;
    size_t _bitCount;
};
位图的应用
  1. 快速查找某个数据是否在一个集合中

  2. 排序

  3. 求两个集合的交集、并集等

  4. 操作系统中磁盘块标记

位图习题:

  1. 给一个超过100G大小的log file, log中存着IP地址, 设计算法找到出现次数最多的IP地址? 与上题条件相同, 如何找到top K的IP?如何直接用Linux系统命令实现?

Hash分桶法:

将100G文件分成1000份,将每个IP地址映射到相应文件中:file_id = hash(ip) % 1000

在每个文件中分别求出最高频的IP,再合并Hash分桶法;

使用Hash分桶法把数据分发到不同的文件;

各个文件分别统计top K;

Linux系统命令:sort log_file | uniq -c | sort -nr | head -K(得到前几名)
  
2. 给定100亿个整数,设计算法找到只出现一次的整数?

Hash分桶法,将100亿个整数映射到不同的区间,在每个区间中分别找只出现一次的整数。

利用位图(设置两个位图),遍历每个区间的整数,遍历的过程中给现的数在第一个位图中置1,如果遍历的过程中,发现这一位是1,则给第二个位图中此位置置1,这样遍历结束后,对比两个位图,找到只出现一次的整数(第一个位图位置为1,第二个位图位置为0,再还原回去,得到此数)

  1. 给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件交集?

解法1 : 一个int的位存为512MB(这里的整数是32bit的话)

利用位图(设置两个位图,两个文件各给一个位图)。遍历第一个文件给出现的数在第一个位图中置1,遍历第二个文件给出现的数在第二个位图中置1,对比两个位图,找到相同位上皆为1,找到交集。

解法2 : 保险的方法应该是使用:桶分+组内bitmap。

使用hash函数将第一个文件的所有整数映射到1000个文件中,每个文件有1000万个整数,大约40M内存,
内存可以放下,把1000个文件记为 a1,a2,a3…a1000,用同样的hash函数映射第二个文件到1000个文件中,这1000个文件记为b1,b2,b3…b1000,由于使用的是相同的hash函数,所以两个文件中一样的数字会被分配到文件下标一致的文件中,分别对a1和b1求交集,a2和b2求交集,ai和bi求交集,最后将结果汇总,即为两个文件的交集

  1. 1个文件有100亿个int,1G内存,设计算法找到出现次数超过2次的所有整数

Bitmap扩展:用2个bit表示状态,00表示未出现,01出现过1次,10出现过2次,11表示出现过2次以上。

布隆过滤器

布隆过滤器提出

我们在使用新闻客户端看新闻时,它会给我们不停地推荐新的内容,它每次推荐时要去重,去掉那些已经看过的内容。问题来了,新闻客户端推荐系统如何实现推送去重的? 用服务器记录了用户看过的所有历史记录,当推荐系统推荐新闻时会从每个用户的历史记录里进行筛选,过滤掉那些已经存在的记录。 如何快速查找呢?

  1. 用哈希表存储用户记录,缺点:浪费空间
  2. 用位图存储用户记录,缺点:不能处理哈希冲突
  3. 将哈希与位图结合,即布隆过滤器
布隆过滤器概念

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

在这里插入图片描述

在这里插入图片描述

向布隆过滤器中插入:"baidu"

在这里插入图片描述

在这里插入图片描述

// 假设布隆过滤器中元素类型为K,每个元素对应5个哈希函数
template<class K, class KToInt1 = KeyToInt1, class KToInt2 = KeyToInt2,
    class KToInt3 = KeyToInt3, class KToInt4 = KeyToInt4,
    class KToInt5 = KeyToInt5>
    class BloomFilter
{
public:
    BloomFilter(size_t size) : // 布隆过滤器中元素个数 
        _bmp(5*size),
        _size(0)
    {}
    bool Insert(const K& key)
    {
        size_t bitCount = _bmp.Size();
        size_t index1 = KToInt1()(key) % bitCount;
        size_t index2 = KToInt2()(key) % bitCount;
        size_t index3 = KToInt3()(key) % bitCount;
        size_t index4 = KToInt4()(key) % bitCount;
        size_t index5 = KToInt5()(key) % bitCount;
        _bmp.Set(index1); _bmp.Set(index2); _bmp.Set(index3);
        _bmp.Set(index4); _bmp.Set(index5);
        _size++;
    } 
private:
    bitset _bmp;
    size_t _size; // 实际元素的个数 
}
布隆过滤器的查找

布隆过滤器的思想是将一个元素用多个哈希函数映射到一个位图中,因此被映射到的位置的比特位一定为1。 所以可以按照以下方式进行查找:分别计算每个哈希值对应的比特位置存储的是否为零,只要有一个为零, 代表该元素一定不在哈希表中,否则可能在哈希表中。

bool IsInBloomFilter(const K& key)
{
    size_t bitCount = _bmp.Size();
    size_t index1 = KToInt1()(key) % bitCount;
    if (!_bmp.Test(index1))
        return false;
    size_t index2 = KToInt2()(key) % bitCount;
    if (!_bmp.Test(index2))
        return false;
    size_t index3 = KToInt3()(key) % bitCount;
    if (!_bmp.Test(index3))
        return false;
    size_t index4 = KToInt4()(key) % bitCount;
    if (!_bmp.Test(index4))
        return false;
    size_t index5 = KToInt5()(key) % bitCount;
    if (!_bmp.Test(index5))
        return false; 
    return true; // 有可能在
}

注意:布隆过滤器如果说某个元素不存在时,该元素一定不存在,如果该元素存在时,该元素可能存在,因为有些哈希函数存在一定的误判。

比如:在布隆过滤器中查找"alibaba"时,假设3个哈希函数计算的哈希值为:1、3、7,刚好和其他元素的比特位重叠,此时布隆过滤器告诉该元素存在,但实该元素是不存在的。

布隆过滤器删除

布隆过滤器不能直接支持删除工作,因为在删除一个元素时,可能会影响其他元素。

比如:删除上图中"tencent"元素,如果直接将该元素所对应的二进制比特位置0,“baidu”元素也被删除了, 因为这两个元素在多个哈希函数计算出的比特位上刚好有重叠。

一种支持删除的方法:将布隆过滤器中的每个比特位扩展成一个小的计数器,插入元素时给k个计数器(k个哈希函数计算出的哈希地址)加一,删除元素时,给k个计数器减一,通过多占用几倍存储空间的代价来增加删除操作。

缺陷:

  1. 无法确认元素是否真正在布隆过滤器中

  2. 存在计数回绕 (计数器超过上限)

布隆过滤器优点

  1. 增加和查询元素的时间复杂度为:O(K), (K为哈希函数的个数,一般比较小),与数据量大小无关

  2. 哈希函数相互之间没有关系,方便硬件并行运算

  3. 布隆过滤器不需要存储元素本身,在某些对保密要求比较严格的场合有很大优势

  4. 在能够承受一定的误判时,布隆过滤器比其他数据结构有这很大的空间优势

  5. 数据量很大时,布隆过滤器可以表示全集,其他数据结构不能

  6. 使用同一组散列函数的布隆过滤器可以进行交、并、差运算

布隆过滤器缺陷

  1. 有误判率,即存在假阳性(False Position),即不能准确判断元素是否在集合中(补救方法:再建立一个白名单,存储可能会误判的数据)

  2. 不能获取元素本身

  3. 一般情况下不能从布隆过滤器中删除元素

  4. 如果采用计数方式删除,可能会存在计数回绕问题

布隆过滤器习题:

  1. 给两个文件,分别有100亿个query,我们只有1G内存,如何找到两个文件交集?分别给出精确算法和近似算法
  • 精确算法:Hash分桶法

将两个文件中的query hash到N个小文件中,并标明query的来源;

在各个小文件中找到重合的query

将找到的重合query汇总

  • 近似算法:BloomFilter算法

将第一个文件按照布隆过滤器放入位图中,然后从第二个文件中取出数字,在第一个位图查找,能找到则可能为交集。如果有一位找不到,则不存在交集。

  1. 如何扩展BloomFilter使得它支持删除元素的操作

将BloomFilter中的每一位扩展为一个计数器,记录有多少个hash函数映射到这一位;删除的时候,只有当引用计数变为0时,才真正将该位置为0。

倒排索引

正序索引: 文件索引单词(统计某个单词在文件中出了几次)
倒排索引: 单词索引文件(统计单词都在那些文件中出现过)

倒排索引创建索引的流程:

1) 首先把所有的原始数据进行编号,形成文档列表
2) 把文档数据进行分词,得到很多的词条,以词条为索引。保存包含这些词条的文档的编号信息。

题目:(倒排索引,也可)

给上千个文件,每个文件大小为1K—100M。给n个词,设计算法对每个词找到所有包含它的文件,你只有 100K内存

解答:
我们可以使用布隆过滤器来判断一个文件是否包含这n个单词生成n个布隆过滤器放到外存,我们事先定义好一个包含这n个单词信息的文件info,每当我们在一个文件找到一个对应的单词就将这个文件的信息写入info对应单词的位置。我们只有100K内存,这100K内存我们一部分用来存放布隆过滤器一部分可以存放文件,因为文件最小都为100K,所以我们可以尝试把它切分为50K的小文件,每个文件标志好所属的大文件,这样我们每次读入一个布隆过滤器和一个小文件,如果这个文件有对应的单词则在info中标记所属大文件的信息,如果没有则读入下一个布隆过滤器,把所有布隆过滤器都使用后,再读下一个文件重复上述步骤直至把所有文件都遍历完。


如有不同见解,欢迎留言讨论~~

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
(Bitmap)和布隆过滤器(Bloom Filter)都是常用的数据结构,用于处理大规模数据集合,但它们有着不同的应用场景和用途。 位是一种压缩数据结构,用于快速地判断某个元素是否在集合中。位的实现方式是将每个元素映射到一个二进制位上,如果该元素存在于集合中,则将对应的二进制位标记为1,否则标记为0。这样,当需要查询某个元素是否在集合中时,只需要查找对应的二进制位即可。由于位的实现方式非常简单,因此可以快速地进行插入和查询操作,而且占用的空间也非常小,适合处理大规模数据集合。 布隆过滤器也是一种快速判断元素是否存在于集合中的数据结构,但其实现方式与位略有不同。布隆过滤器使用一组哈希函数将元素映射到多个二进制位上,并将对应的二进制位标记为1。当查询某个元素是否在集合中时,将该元素进行哈希映射,并查找对应的二进制位,如果所有的二进制位都被标记为1,则说明该元素可能存在于集合中,否则可以确定该元素不存在于集合中。布隆过滤器的优点是可以快速地判断一个元素不存在于集合中,而且占用的空间也比较小,但存在误判率的问题。 因此,位布隆过滤器虽然都可以用来处理大规模数据集合,但它们的实现方式和应用场景有所不同。位适用于需要快速地判断某个元素是否在集合中的场景,而布隆过滤器适用于需要快速地判断一个元素不存在于集合中的场景。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值