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

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

👉哈希函数👈


引起哈希冲突的一个原因可能是:哈希函数设计不够合理。


哈希函数的设计原则

哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表允许有 m 个地址时,其值域必须在 0 到 m-1 之间

哈希函数计算出来的地址能均匀分布在整个空间中

哈希函数应该比较简单


常见哈希函数


直接定址法(常用)

取关键字的某个线性函数为散列地址:Hash(Key) = A * Key + B

优点:简单、均匀且不存在哈希冲突

缺点:需要事先知道关键字的分布情况

使用场景:适合查找比较小且连续的情况

面试题:字符串中第一个只出现一次字符

除留余数法(常用)

设散列表中允许的地址数为 m,取一个不大于 m,但最接近或者等于 m 的质数 p 作为除数,按照哈希函数:Hash(key) = key% p(p <= m),将关键码转换成哈希地址。除留余数法存在哈希冲突,重点解决哈希冲突。

平方取中法(了解)

假设关键字为 1234,对它平方就是 1522756,抽取中间的 3 位 227 作为哈希地址;再比如关键字为 4321,对它平方就是 18671041,抽取中间的 3 位 671 (或710)作为哈希地址。平方取中法比较适合:不知道关键字的分布,而位数又不是很大的情况。

折叠法是将关键字从左到右分割成位数相等的几部分(最后一部分位数可以短些),然后将这几部分叠加求和,并按散列表表长,取后几位作为散列地址。折叠法适合事先不需要知道关键字的分布,适合关键字位数比较多的情况,只适用于整数。

7c27657dba754a97b8e6e52b288e0683.png

5. 随机数法(了解)

选择一个随机函数,取关键字的随机函数值为它的哈希地址,即 H(key) = random(key),其中 random 为随机数函数。通常应用于关键字长度不等时采用此法。

6. 数学分析法(了解)

设有 n 个 d 位数,每一位可能有 r 种不同的符号,这 r 种不同的符号在各位上出现的频率不一定相同,可能在某些位上分布比较均匀,每种符号出现的机会均等,在某些位上分布不均匀只有某几种符号经常出现。可根据散列表的大小,选择其中各种符号分布均匀的若干位作为散列地址。数字分析法通常适合处理关键字位数比较大的情况,如果事先知道关键字的分布且关键字的若干位分布较均匀的情况。例如:

35e29128923a4e11a00467c3479e86f6.png

假设要存储某家公司员工登记表,如果用手机号作为关键字,那么极有可能前 7 位都是相同的,那么我们可以选择后面的四位作为散列地址,如果这样的抽取工作还容易出现 冲突,还可以对抽取出来的数字进行反转(如 1234 改成 4321 )、右环位移(如 1234 改成 4123 )、左环移位、前两数与后两数叠加(如 1234 改成 12+34=46 )等方法。


注意:哈希函数设计的越精妙,产生哈希冲突的可能性就越低,但是无法避免哈希冲突。


👉位图👈


位图概念


面试题

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

遍历,时间复杂度O(N)

排序(O(NlogN)),利用二分查找: logN

位图解决

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

f494e69a674a485ca055c6e25e146ab2.png90e7b0015b054b0885c3c2c888e42db8.png

 

2. 位图概念

所谓位图,就是用每一位来存放某种状态,适用于海量数据,数据无重复的场景。通常是用来判断某个数据存不存在的。注意:位图所开空间要大于或等于整型的范围,因为有可能有些数字数,有些数字小。


位图的实现


位图最核心的三个节点是setresettestset是将 x 对应的比特位设置为 1,reset是将 x 对应的比特位设置为 0,test是查看 x 在不在。a991b2681cc2420c82488e32c4b15ba5.png


#pragma once
namespace Joy
{
  template<size_t N>
  class bitset
  {
  public:
    bitset()
    {
      _bits.resize(N / 8 + 1, 0); // 多开一个字节,防止越界
    }
    // 将比特位设置为1
    void set(size_t x)
    {
      size_t i = x / 8;
      size_t j = x % 8;
      _bits[i] |= (1 << j);
    }
    // 将比特位设置为0
    void reset(size_t x)
    {
      size_t i = x / 8;
      size_t j = x % 8;
      _bits[i] &= ~(1 << j);
    }
    // 查x在不在
    bool test(size_t x)
    {
      size_t i = x / 8;
      size_t j = x % 8;
      return (_bits[i] & (1 << j));
    }
  private:
    vector<char> _bits;
  };
  void BitSetTest()
  {
    bitset<100> bs;
    bs.set(8);
    bs.set(9);
    bs.set(20);
    cout << bs.test(8) << endl;
    cout << bs.test(9) << endl;
    cout << bs.test(20) << endl;
    bs.reset(8);
    bs.reset(9);
    bs.reset(20);
    cout << bs.test(8) << endl;
    cout << bs.test(9) << endl;
    cout << bs.test(20) << endl;
  }
  void BitSetTest2()
  {
    // 开出42亿9千万个比特位
    bitset<-1> bs1; 
    bitset<0xffffffff> bs2;
  }
}

b74df8cf260f456f9238de3f7d8d2818.png


f7eff7b737544e5ea66a99f1e2dfc443.png


位图的应用


  1. 快速查找某个数据是否在一个集合中
  2. 排序 + 去重
  3. 求两个集合的交集、并集等
  4. 操作系统中磁盘块标记


题目一:给定 100 亿个整数,设计算法找到只出现一次的整数? 这种题目是 KV 的统计次数搜索模型,一个数字出现次数有三种情况:出现零次、出现一次以及出现两次及以上,这三种情况只需要用两个比特位就可以表示。


5dae1e560dcc429ebae31b083b3a94c8.png

  template <size_t N>
  class twobitset
  {
  public:
    void set(size_t x)
    {
      bool inset1 = _bs1.test(x);
      bool inset2 = _bs2.test(x);
      // 00
      if(inset1 == false && inset2 == false)
      {
        // -> 01
        _bs2.set(x);
      }
      else if (inset1 == false && inset2 == true) // 01
      {
        // -> 10
        _bs1.set(x);
        _bs2.reset(x);
      }
      else if (inset1 == true && inset2 == false)
      {
        // -> 11
        _bs2.set(x);
      }
      // else是x出现三次及三次以上
    }
    void print_once_num()
    {
      for (size_t i = 0; i < N; ++i)
      {
        if (_bs1.test(i) == false && _bs2.test(i) == true)
          cout << i << " ";
      }
      cout << endl;
    }
  private:
    bitset<N> _bs1;
    bitset<N> _bs2;
  };
  void BitSetTest3()
  {
    int a[] = { 3, 4, 5, 2, 3, 4, 4, 4, 4, 12, 77, 65, 44, 4, 44, 99, 33, 33, 33, 6, 5, 34, 12 };
    twobitset<100> bs;
    for (auto e : a)
    {
      bs.set(e);
    }
    bs.print_once_num();
  }

89e938e4bb9c4789993c349fa296630e.png


相关文章
|
4月前
|
存储 搜索推荐 Serverless
【C++航海王:追寻罗杰的编程之路】哈希的应用——位图 | 布隆过滤器
【C++航海王:追寻罗杰的编程之路】哈希的应用——位图 | 布隆过滤器
42 1
|
5月前
|
存储 算法 数据挖掘
【C++】位图
【C++】位图
50 1
|
7月前
|
存储 人工智能 算法
从C语言到C++_32(哈希的应用)位图bitset+布隆过滤器+哈希切割(下)
从C语言到C++_32(哈希的应用)位图bitset+布隆过滤器+哈希切割
49 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`类的应用。
41 2
|
1月前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
83 5
|
1月前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
81 4