【C++】位图 | 布隆过滤器(下)

简介: 【C++】位图 | 布隆过滤器(下)

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


91e3d47a8b7d48849dfa153b37048d37.png

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


f6538303d55c4a29b1421749155b52c4.png


注:位图只能处理整形。采用位图标记字符串时,必须先将字符串转化为整形的数字,找到位图中对应的比特 位,但是在字符串转整形的过程中,可能会出现不同字符串转化为同一个整形数字,即冲突,因此一般不会直接用位图处理字符串。


👉布隆过滤器👈


布隆过滤器的提出


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


用哈希表存储用户记录,缺点:浪费空间。

用位图存储用户记录,缺点:位图一般只能处理整形,如果内容编号是字符串,就无法处理了。

将哈希与位图结合,即布隆过滤器。


布隆过滤器概念


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

529ac1148f4e49269cd51267b0bf3b73.png


布隆过滤器的实现


1. 查找


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

0b7e71f4b07249d0bc53d15b7c866064.png


2f5d62411fd24bc19326757b0fbd8f1c.png

布隆过滤器的应用场景


f5f4571851954ecabcbcdd99b4c01093.png


布隆过滤器拓展资料:链接 ,字符串哈希算法:链接


布隆过滤器的公式


352abb951b784b509b3a55cd25aafd3e.png


#pragma once
#include "BitSet.h"
namespace Joy
{
  // 哈希函数
  struct HashBKDR
  {
    // BKDR
    size_t operator()(const string& key)
    {
      size_t val = 0;
      for (auto ch : key)
      {
        val *= 131;
        val += ch;
      }
      return val;
    }
  };
  struct HashAP
  {
    // AP
    size_t operator()(const string& key)
    {
      size_t hash = 0;
      for (size_t i = 0; i < key.size(); i++)
      {
        if ((i & 1) == 0)
        {
          hash ^= ((hash << 7) ^ key[i] ^ (hash >> 3));
        }
        else
        {
          hash ^= (~((hash << 11) ^ key[i] ^ (hash >> 5)));
        }
      }
      return hash;
    }
  };
  struct HashDJB
  {
    // DJB
    size_t operator()(const string& key)
    {
      size_t hash = 5381;
      for (auto ch : key)
      {
        hash += (hash << 5) + ch;
      }
      return hash;
    }
  };
  // N表示准备要映射N个值,布隆过滤器处理的类型通常是字符串
  template<size_t N,
    class K = string, class Hash1 = HashBKDR, class Hash2 = HashAP, class Hash3 = HashDJB>
  class BloomFilter
  {
  public:
    void set(const K& key)
    {
      size_t hash1 = Hash1()(key) % (_ratio * N);
      _bits.set(hash1);
      size_t hash2 = Hash2()(key) % (_ratio * N);
      _bits.set(hash2);
      size_t hash3 = Hash3()(key) % (_ratio * N);
      _bits.set(hash3);
    }
    bool test(const K& key)
    {
      size_t hash1 = Hash1()(key) % (_ratio * N);
      if (!_bits.test(hash1))
        return false; // 准确的
      size_t hash2 = Hash2()(key) % (_ratio * N);
      if (!_bits.test(hash2))
        return false; // 准确的
      size_t hash3 = Hash3()(key) % (_ratio * N);
      if (!_bits.test(hash3))
        return false;  // 准确的
      return true; // 可能存在误判
    }
    // 能否支持删除? ->布隆过滤器一般不支持删除,如果要
    // 删除的话,可以加上映射位置的引用计数。
    void reset(const K& key);
  private:
    const static size_t _ratio = 5; // _ratio为倍率
    bitset<_ratio* N> _bits;  // 使用自己实现的bitset
  };
  void BloomFilterTest1()
  {
    BloomFilter<10> bf;
    string arr1[] = { "苹果", "西瓜", "阿里", "美团", "苹果", "字节", "西瓜", "苹果", "香蕉", "苹果", "腾讯" };
    for (auto& str : arr1)
    {
      bf.set(str);
    }
    string arr2[] = { "苹果111", "西瓜", "阿里2222", "美团", "苹果dadcaddxadx", "字节", "西瓜sSSSX", "苹果 ", "香蕉", "苹果$", "腾讯" };
    for (auto& str : arr2)
    {
      cout << str << ":" << bf.test(str) << endl;
    }
  }
  void BloomFilterTest2()
  {
    srand(time(0));
    const size_t N = 1000000;
    BloomFilter<N> bf;
    cout << sizeof(bf) << endl;
    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(1234 + i));
    }
    for (auto& str : v1)
    {
      bf.set(str);
    }
    // 相似
    std::vector<std::string> v2;
    for (size_t i = 0; i < N; ++i)
    {
      std::string url = "http://www.cnblogs.com/-clq/archive/2021/05/31/2528153.html";
      url += std::to_string(rand() + 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(rand() + i);
      v3.push_back(url);
    }
    size_t n3 = 0;
    for (auto& str : v3)
    {
      if (bf.test(str))
      {
        ++n3;
      }
    }
    cout << "不相似字符串误判率:" << (double)n3 / (double)N << endl;
  }
}

b873f5a93e57425d95f8ff18b402e292.png

1d2f9677296d4acca3b2498b2a3c7cad.png


存储空间越大,布隆过滤器的误判率就会越低。注:库里的 bitset 是静态数组,空间太大容易栈溢出,可以在堆上开空间。


2. 删除


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

d73afd05b64c439fb1f03e7a23c714a1.png

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



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



布隆过滤器优点和缺点


优点


  1. 增加和查询元素的时间复杂度为:O(K), (K 为哈希函数的个数,一般比较小),与数据量大小无关
  2. 哈希函数相互之间没有关系,方便硬件并行运算
  3. 布隆过滤器不需要存储元素本身,在某些对保密要求比较严格的场合有很大优势
  1. 在能够承受一定的误判时,布隆过滤器比其他数据结构有这很大的空间优势
  2. 数据量很大时,布隆过滤器可以表示全集,其他数据结构不能
  3. 使用同一组散列函数的布隆过滤器可以进行交、并、差运算

缺点

  1. 有误判率,即存在假阳性(False Position),即不能准确判断元素是否在集合中(补救方法:再建立一个白名单,存储可能会误判的数据)
  2. 不能获取元素本身
  3. 一般情况下不能从布隆过滤器中删除元素
  4. 如果采用计数方式删除,可能会存在计数回绕问题


布隆过滤器的题目


题目一:给两个文件,分别有 100 亿个 query,我们只有 1G 内存,如何找到两个文件交集?分别给出精确算法和近似算法。 query 是查询,常见的查询有:网络请求、SQL 语句等,它们都是字符串。近似算法允许一些误判,那么我们可以先将一个文件的 query 放入布隆过滤器中,再去查另一个文件的 query 在不在布隆过滤器中,就可以找到两个文件的交集了(还需要去重)。精确算法如下图所示:


2fc65dadd2d2487f8b4c510033772af6.png


题目二:如何扩展BloomFilter使得它支持删除元素的操作?布隆过滤器一般是不支持删除的,支持删除可能会影响其他值的查询。如果想要支持删除,可以添加引用计数。但是支持了删除,空间消耗就更多,优势就变小了。


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

9d79da7b018d4f5a8d6e610c7a34f331.png


Linux 系统命令实现

e027d8b71ce147aa8b1b7c7f74ae5a35.png

353e7bf28d844745945839651237917a.png


注:哈希切分并不是平均切分,而是相同的哈希值的字符串等进入到相同的位置。


哈希的应用非常地广泛,服务器存储中也使用到了哈希。

bd0379b69e524fa4bc76b0ae6c2c2ddb.png



👉总结👈


本篇博客主要讲解了常见的哈希函数,什么是位图、位图的实现和应用、什么是布隆过滤器、布隆过滤器的实现和优缺点以及哈希切分等等。那么以上就是本篇博客的全部内容了,如果大家觉得有收获的话,可以点个三连支持一下!谢谢大家!💖💝❣️


相关文章
|
4月前
|
存储 搜索推荐 Serverless
【C++航海王:追寻罗杰的编程之路】哈希的应用——位图 | 布隆过滤器
【C++航海王:追寻罗杰的编程之路】哈希的应用——位图 | 布隆过滤器
42 1
|
5月前
|
存储 算法 数据挖掘
【C++】位图
【C++】位图
50 1
|
7月前
|
存储 人工智能 算法
从C语言到C++_32(哈希的应用)位图bitset+布隆过滤器+哈希切割(下)
从C语言到C++_32(哈希的应用)位图bitset+布隆过滤器+哈希切割
50 1
|
7月前
|
存储 算法 数据库
从C语言到C++_32(哈希的应用)位图bitset+布隆过滤器+哈希切割(中)
从C语言到C++_32(哈希的应用)位图bitset+布隆过滤器+哈希切割
61 1
|
7月前
|
存储 C语言 C++
从C语言到C++_32(哈希的应用)位图bitset+布隆过滤器+哈希切割(上)
从C语言到C++_32(哈希的应用)位图bitset+布隆过滤器+哈希切割
60 1
|
7月前
|
存储 监控 算法
【C++】哈希(位图、布隆过滤器)
【C++】哈希(位图、布隆过滤器)
|
7月前
|
算法 Go 数据库
数据结构/C++:位图 & 布隆过滤器
数据结构/C++:位图 & 布隆过滤器
46 0
|
25天前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
42 2
|
1月前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
83 5
|
1月前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
81 4