C++ 哈希的应用【布隆过滤器】

慈云数据 2024-03-13 技术支持 92 0

✨个人主页: 北 海

🎉所属专栏: C++修行之路

🎃操作环境: Visual Studio 2022 版本 17.6.5

成就一亿技术人


文章目录

  • 🌇前言
  • 🏙️正文
    • 1、字符串比较
    • 2、布隆过滤器的概念
    • 3、布隆过滤器的实现
      • 3.1、基本结构
      • 3.2、插入
      • 3.3、查找
      • 3.4、删除
      • 3.5、测试
      • 3.6、优化方案
      • 4、布隆过滤器小结
      • 5、海量数据面试题(哈希切割)
        • 5.1、题目一
        • 5.2、题目二
        • 🌆总结

          🌇前言

          注册账号是进行网络冲浪的第一步操作,而拥有一个具有个性且独一无二的用户昵称是非常重要的,很多人在填写昵称时,常常会看到 此昵称已存在 的提示,系统是如何快速知道当前昵称是否存在呢?总不能挨个去遍历对比吧,这时候就需要我们本文中的主角: 布隆过滤器

          图示


          🏙️正文

          1、字符串比较

          常见的字符串比较方法是 按 ASCII 码值进行比较,直到两个字符串同时结束,说明两者一致

          比如字符串1 abcdef 和字符串2 azbmcy

          显然两个字符串不一样

          这种比较方法很直接,也很可靠,但缺点也很明显:需要对字符串进行遍历

          一个字符串还好,如果是几千万个字符串呢?不但需要消耗大量存储空间,查找效率也很低,此时填写个昵称,服务器都要跑一会才有反映,这是用户所无法容忍的

          因此人们想出了另一个方法,利用哈希映射 的思想,计算出 哈希值,存储这个值即可,可以借此 标识字符串是否存在

          在进行字符串(昵称)比较时,只需要计算出对应的 哈希值,然后看看该位置是否存在即可

          哈希值 也是一个整数啊,可以利用 位图 进行 设置,查找字符串时,本质上是在 查找哈希值是否在位图中存在

          字符串有千万种组合,但字符是有限的,难免会出现 误判 的情况(此处的 哈希函数 为每个字符相加)

          图示

          为了尽可能降低 误判率,在 位图 的基础之上设计出了 布隆过滤器

          接下来看看什么是 布隆过滤器 吧

          图示


          2、布隆过滤器的概念

          这里是 布隆 可不是 英雄联盟中的 弗雷尔卓德之心 布隆,毕竟他也不能解决字符串比较问题,他只是 召唤师峡谷 中的一个坦克,主要负责 过滤(吸收) 敌方的伤害

          布隆过滤器 是由 布隆(Burton Howard Bloom) 在 1970 年提出的一种 紧凑型的、比较巧妙 的 概率型数据结构,特点是 高效地插入和查询

          布隆过滤器 的核心在于通过添加 哈希函数 来 降低误判率

          举个例子,如果每个人的名字都只有一个字,那么肯定存在很多重名的情况,但如果把名字字数增多,重复的情况就会大大缓解

          所以 布隆过滤器 其实很简单,无非就是映射字符串时,多安排几个不一样的 哈希函数,多映射几个 比特位,只有当每个 比特位 的为 1 时,才能验证这个字符串是存在的

          图示


          3、布隆过滤器的实现

          3.1、基本结构

          布隆过滤器 离不开 位图,此时可以搬出之前实现过的 位图结构

          既然需要增加 哈希函数,我们可以在模板中添加三个 哈希函数 的模板参数以及待存储的数据类型 K

          namespace Yohifo
          {
          	template
          	class BloomFilter
          	{
          	public:
          		//……
          	private:
          		Yohifo::bitset _bits;	//位图结构
          	};
          }
          

          显然,这三个 哈希函数 的选择是十分重要的,我们在这里提供三种较为优秀的 哈希函数(字符串哈希算法),分别是 BKDRHash、APHash 以及 DJBHash

          函数原型如下(写成 仿函数 的形式,方便传参与调用):

          struct BKDRHash
          {
              size_t operator()(const std::string& str)
              {
                  size_t hash = 0;
                  for (auto e : str)
                  {
                      hash = hash * 131 + (size_t)e;
          		}
          		
                  return hash;
              }
          };
          struct APHash
          {
              size_t operator()(const std::string& str)
              {
                  size_t hash = 0;
                  for (auto e : str)
                  {
                      if (((size_t)e & 1) == 0)
                      {
                          hash ^= ((hash > 3));
                      }
                      else
                      {
                          hash ^= (~((hash > 5)));
                      }
                  }
                  return hash;
              }
          };
          struct DJBHash
          {
              size_t operator()(const std::string& str)
              {
                  if (str.empty())
                      return 0;
                  size_t hash = 5381;
                  for (auto e : str)
                  {
                      hash += (hash 
              size_t HashI1 = Hash1()(key) % N;   //% N 是为了避免计算出的哈希值过大
              _bits.set(HashI1);
              size_t HashI2 = Hash2()(key) % N;
              _bits.set(HashI2);
              size_t HashI3 = Hash3()(key) % N;
              _bits.set(HashI3);
          }
          
               //过滤不存在的情况,至于是否存在,还得进一步判断
               size_t HashI1 = Hash1()(key) % N;
               if (_bits.test(HashI1) == false)
                   return false;
               size_t HashI2 = Hash2()(key) % N;
               if (_bits.test(HashI2) == false)
                   return false;
               size_t HashI3 = Hash3()(key) % N;
               if (_bits.test(HashI3) == false)
                   return false;
               //经过层层过滤后,判断字符串可能存在
               return true;
           }
          
              BloomFilter
             //测试误判率
             //构建一组字符串 + 一组相似字符串 + 一组完全不同字符串
             //通过 test 测试误判率
             const size_t N = 100000;	//字符串数
             std::string str = "https://blog.csdn.net/weixin_61437787?spm=1000.2115.3001.5343";
             //构建原生基本的字符串
             std::vector
                 std::string url = str + std::to_string(i);
                 vsStr[i] = url;	//保存起来,后续要用
             }
             //构建相似的字符串
             std::vector
                 std::string url = str + std::to_string(i * -1);
                 vsSimilarStr[i] = url;
                 bfSimilarStr.set(url);
             }
             //构建完全不一样的字符串
             str = "https://leetcode.cn/problemset/all/";
             std::vector
                 std::string url = str + std::to_string(i);
                 vsDiffStr[i] = url;
                 bfDiffStr.set(url);
             }
             //误判率检测:原生 
                 if (bfSimilarStr.test(e) == true)
                     missVal++;
             }
             //误判率检测:原生 
                 if (bfDiffStr.test(e) == true)
                     diffVal++;
             }
             std::cout 
                 static const int _len = 6;   //布隆过滤器的长度
                 static const int _size = N * _len; //位图的大小
          public:
                 void set(const K& key)
                 {
                     size_t HashI1 = Hash1()(key) % _size;   //% N 是为了避免计算出的哈希值过大
                     _bits.set(HashI1);
                     size_t HashI2 = Hash2()(key) % _size;
                     _bits.set(HashI2);
                     size_t HashI3 = Hash3()(key) % _size;
                     _bits.set(HashI3);
                 }
                 bool test(const K& key)
                 {
                     //过滤不存在的情况,至于是否存在,还得进一步判断
                     size_t HashI1 = Hash1()(key) % _size;
                     if (_bits.test(HashI1) == false)
                         return false;
                     size_t HashI2 = Hash2()(key) % _size;
                     if (_bits.test(HashI2) == false)
                         return false;
                     size_t HashI3 = Hash3()(key) % _size;
                     if (_bits.test(HashI3) == false)
                         return false;
                     //经过层层过滤后,判断字符串可能存在
                     return true;
                 }
          private:
          	Yohifo::bitset
微信扫一扫加客服

微信扫一扫加客服

点击启动AI问答
Draggable Icon