【C++进阶08】哈希的应用(位图and布隆过滤器)

在这里插入图片描述

一、位图

1.1 位图的概念

面试题

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

能想到的解决思路:

  1. 遍历,时间复杂度O(N)
  2. 排序(O(NlogN)) + 利用二分查找: logN
  3. 放到哈希表或红黑树

40亿整数就是16GB,无法全部加载到内存
遍历、排序和二分查找就都不太现实
虽然可以在文件中归并,但就慢了很多
文件中不能用下标,自然无法二分查找

虽然可以将数据一段一段放进哈希表和红黑树
但每次将数据插入进红黑树又释放
相当于暴力查找40亿数据
红黑树的特性完全没用上

所以以上3点都是不合适的
最大的原因就是内存不足

位图解决
数据是否在给定的整形数据中
结果是在或者不在,刚好是两种状态
那么可以用比特位表示数据是否存在
1为存在,0为不存在

比如数据{1,2,4,9,1517,23}在位图的样子
在这里插入图片描述
所谓位图,就是用每一位来存放某种状态
适用于海量数据,数据无重复的场景
通常是用来判断某个数据存不存在的

一个比特位就能表示一个整型数据在或不在
一个整型就是32比特位,相当于缩小了32倍
也就是说16G的数据只需要0.5G

1.2 位图的模拟实现

在这里插入图片描述
位图的三个主要接口:

  1. set:将数据映射位置置成1,表示存在
  2. reset:将数据映射位置置成0,表示删除
  3. test:检测数据是否存在于位图
template <size_t N>
class bitset
{
public:bitset(){_bits.resize(N / 8 + 1, 0); // 需求N个比特位,按字节给,所以除8.除会去余,所以加1}void set(size_t x) // 将x比特位置1{size_t i = x / 8; // 计算x映射的位在第i个char数组位置size_t j = x % 8; // 计算x映射的位在这个char的第j个比特位_bits[i] |= (1 << j);}void reset(size_t x) // 将x比特位置0{size_t i = x / 8;size_t j = x % 8;_bits[i] &= ~(1 << j);}bool test(size_t x) // 检测位图中x是否为1{size_t i = x / 8; size_t j = x % 8; return _bits[i] & (1 << j);}
private:vector<char> _bits;
};

1.3 位图的应用

1、给定100亿个整数,设计算法找到只出现一次的整数?

方法:

  1. 创建两个位图对象bs1、bs2
  2. 遍历数据
    出现0次用00表示
    出现1次用01表示
    出现2次用10表示
    出现3次及以上用11表示

如果数据映射的位置在bs1里为1
在bs2里为0表示此数据出现过2次
如果在bs1和bs2里都为1,表示出现3次及以上
方法实现:

template <size_t N>
class twobitset
{
public:void set(size_t x) {// 00 -> 01if (_bs1.test(x) == false && _bs2.test(x) == false){_bs2.set(x);}else if (_bs1.test(x) == false && _bs2.test(x) == true){// 01 -> 10_bs1.set(x);_bs2.reset(x);}// 10 不变}void print(){for (size_t i = 0; i < N; i++) {if (_bs2.test(i))cout << i << " ";}}
private:bitset<N> _bs1;bitset<N> _bs2;
};void test_twobitset3()
{twobitset<1000> bs;int a[] = { 0, 12, 12, 0, 20, 12, 12, 0, 223, 22, 45, 4 };for (auto e : a){bs.set(e);}bs.print();
}

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

方法1:
其中的一个文件读到内存的位图中
再读另一个文件,判断在不在上面的位图
在就是交集

问题:
找出的交集存在重复值

解决方法1:
一个数为交集就在第一个文件set那个数

解决方法2:
读取文件1映射到位图1
读取文件2映射到位图2
判断数据映射的位置在这两个位图中
是否都为1

for (int i = 0; i < N; i++)
{if (bs1.test(i) && bs2.test(i)){// 交集}
}

或者按位与这两个位图

3、位图应用变形:1个文件有100亿个int
1G内存,设计算法找到出现次数不超过2次的所有整数

定义两个位图对象
将数据插入到位图
出现0次用00表示
出现1次用01表示
出现2次用10表示
出现3次及以上用11表示

除了两个位图对应位置都为1
其他都打印

二、布隆过滤器

位图的优点:

  1. 速度快、节省空间

缺点:

  1. 只能映射整形,其他如浮点数、string等
    不能存储映射

布隆过滤器便是解决此缺点

2.1 布隆过滤器提出

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

  1. 用哈希表存储用户记录
    缺点:浪费空间
  2. 用位图存储用户记录
    缺点:位图一般只能处理整形
    如果内容编号是字符串
    就无法处理了
  3. 将哈希与位图结合,即布隆过滤器

2.2 布隆过滤器概念

布隆过滤器是由布隆(Burton Howard Bloom)
在1970年提出的 一种紧凑型的、比较巧妙的概
率型数据结构,特点:高效地插入和查询
可以用来告诉你
“某样东西一定不存在或者可能存在”
它是用多个哈希函数
将一个数据映射到位图结构中
此种方式不仅可以提升查询效率
也可以节省大量的内存空间
在这里插入图片描述
用多个哈希函数将字符映射到不同的位置
以此降低重复率,查找时在所有映射的位置
查看是否均为1

查找一个值在与不在
在:是不准确的,存在误判
不在:是准确的
比如美团,本来不在
查找时每个映射的位置都跟别人冲突
导致认为它在

2.3 布隆过滤器的使用场景

  1. 能容忍误判场景
    比如:改名时,快速判断昵称是否使用过

昵称在数据库,而数据库在磁盘
如果去磁盘查找修改的昵称是否使用过
效率非常慢,我们平时改昵称时
只要输入就能立即反馈昵称是否使用

这时可以把所有用户昵称放入布隆过滤器
即使误判修改昵称已使用,用户也感知不到
(只要误判率不高还是可以接受的)

2.4 布隆过滤器的实现

如何选择哈希函数个数和布隆过滤器长度
k 为哈希函数个数
m 为布隆过滤器长度
n 为插入的元素个数
p 为误报率
在这里插入图片描述
在这里插入图片描述
代码实现:

struct BKDRHash
{size_t operator()(const string& s){size_t hash = 0;for (auto ch : s){hash = hash * 30 + ch;}return hash;}};struct APHash
{size_t operator()(const string& s) // 仿函数的作用:把一个类当作对象去访问或把一个对象像函数去使用{size_t hash = 0; // 加register放到最前面表示建议变量放到寄存器里面for (long i = 0; i < s.size(); i++){size_t ch = s[i];if ((i & 1) == 0) // i 是偶数走if,i 是奇数else.奇数二进制的低位一定是1,按位与1得到的便是奇数hash ^= ((hash << 7) ^ ch ^ (hash >> 3));elsehash ^= (~((hash << 11) ^ ch ^ (hash >> 5)));}return hash;}
};struct DJBHash
{size_t operator()(const string& s){size_t hash = 5381;for (auto ch : s){hash += (hash << 5) + ch;}return hash;}
};// N是key,插入值的个数
template<size_t N, 
class K = string,
class Hash1 = BKDRHash,
class Hash2 = APHash,
class Hash3 = DJBHash>
class BloomFilter
{
public:void set(const K& key){size_t len = N * _X;size_t hash1 = Hash1()(key) % len; // 用Hash仿函数转成可以取模的整型值,模N是怕转出来的值超出N_bs.set(hash1);size_t hash2 = Hash2()(key) % len;_bs.set(hash2);size_t hash3 = Hash3()(key) % len;_bs.set(hash3);cout << hash1 << " " << hash2 << " " << hash3 << endl;}bool test(const K& key) // 判断3个位置,有一个位置为0就是不在{size_t len = N * _X;size_t hash1 = Hash1()(key) % len; if (!_bs.test(hash1))return false;size_t hash2 = Hash2()(key) % len;if (!_bs.test(hash2))return false;size_t hash3 = Hash3()(key) % len;if (!_bs.test(hash3))return false;return true;}
private:static const size_t _X = 6; // 比重为6,冲突率5%以内.比重建议5-10(冲突率1%-10%)bitset<_X * N> _bs; // 开_X倍的空间,空间开得越大,冲突率越低
};

2.5 布隆过滤器的应用

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

100亿query:假设每个query平均50byte
100亿就是5000亿byte,也就是大约占用500G

  1. 可以把每个文件分成1000份读进内存
    每份是0.5个G

在这里插入图片描述
B文件的每一份在A文件的每一份里去找
但是这样时间复杂度太高了
于是可以用哈希函数来切分文件
哈希切分:i = HashFunc(query) % 1000
每个query,算出i是多少就进入Ai小文件
B也是一样,算出i放进Bi小文件
B0、B1……在对应的A0、A1……
小文件里去找,找到了就是交集

跟之前的哈希桶很像
进入同一个桶的都是冲突的值
在这里A和B相同的值用的同一个哈希函数
便一定会进入同一个编号的文件
在这里插入图片描述
会导致的问题:
因为不是平均切分,可能会出现冲突多
每个Ai、Bi小文件过大

  1. 单个文件大量重复query
  2. 单个文件大量不同query

解决方法:
直接使用unordered_set/set
依次读取文件query,插入set中

  1. 如果读取整个小文件query
    都可以成功插入set,说明是情况1
  2. 如果读取整个小文件query
    插入过程抛异常则是情况2
    换其他哈希函数,再次分割求交集

说明:set插入key如果已经有了返回false
如果内存不足抛bad_alloc异常
剩下的都会成功

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

删除一个元素可能会影响其它元素
用多个位图计数的方式表示每个位置被映射了几次
删除时减减该位置

3、哈希切割
给一个超过100G大小的log file, log中存着IP地址,
设计算法找到出现次数最多的IP地址?
与上题条件相同,如何找到top K的IP?

解决方法:
哈希切分成500个小文件
依次读取数据,i = HashFunc(ip)%500
这个ip就是第i个小文件

依次处理每个小文件
使用unordered_map/map统计ip出现次数

  1. 统计过程抛异常,说明单个文件过大
    冲突太多,需要重新换哈希函数
    再次哈希切分这个小文件
  2. 如果没有抛异常,则正常统计
    统计完一个小文件,记录最大的
    clear map,再统计下一个小文件

总结特点:
相同的ip一定进入相同的小文件
读取单个小文件就可以统计ip出现次数

在这里插入图片描述
本篇博客完,感谢阅读🌹
如有错误之处可评论指出
博主会耐心听取每条意见

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/658652.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

242. 有效的字母异位词(力扣)(C语言题解)

✨欢迎来到脑子不好的小菜鸟的文章✨ &#x1f388;创作不易&#xff0c;麻烦点点赞哦&#x1f388; 所属专栏&#xff1a;刷题 我的主页&#xff1a;脑子不好的小菜鸟 文章特点&#xff1a;关键点和步骤讲解放在 代码相应位置 前提&#xff1a; 看本文章之前&#xff0c;建…

FFmpeg和Monibuka拉取rtsp(大华摄像头)视频流时未进行URLCode编码导致提示404等报错

场景 Monibucav4(开源流媒体服务器)在Windows上搭建rtmp服务器并实现拉取rtsp视频流以及转换flv播放&#xff1a; Monibucav4(开源流媒体服务器)在Windows上搭建rtmp服务器并实现拉取rtsp视频流以及转换flv播放_monibuca 搭建流媒体服务-CSDN博客 Nginx搭建RTMP服务器FFmpeg…

Airflow原理浅析

⭐️ airflow基本原理 Apache Airflow 是一个开源的工作流自动化工具&#xff0c;它用于调度和管理复杂的数据工作流。Airflow 的原理基于有向无环图&#xff08;DAG&#xff09;的概念&#xff0c;它通过编写和组织任务的有向图来描述工作流程。 以下是 Apache Airflow 的一…

Linux第40步_移植ST公司uboot的第1步_创建配置文件_设备树_修改电源管理和sdmmc节点

ST公司uboot移植分两步走&#xff1a; 第1步&#xff1a;完成“创建配置文件&#xff0c;设备树&#xff0c;修改电源管理和sdmmc节点&#xff0c;以及shell脚本和编译”。 第2步“完成”修改网络驱动、USB OTG设备树和LCD驱动&#xff0c;以及编译和烧写测试“。 移植太复杂…

【c语言】简单贪吃蛇的实现

目录 一、游戏说明 ​编辑 二、地图坐标​ ​编辑 三、头文件 四、蛇身和食物​ 五、数据结构设计​ 蛇节点结构如下&#xff1a; 封装一个Snake的结构来维护整条贪吃蛇&#xff1a;​ 蛇的方向&#xff0c;可以一一列举&#xff0c;使用枚举&#xff1a; 游戏状态&a…

[Vue3] useRoute、useRouter

useRoute 返回当前路由地址。相当于在模板中使用 $route。必须在 setup() 中调用。用于在组件中获取当前路由的信息&#xff0c;返回一个包含路由信息的对象。这个函数适用于那些不需要监听路由变化的场景&#xff0c;只是获取当前路由信息的静态数据。 useRouter 返回 route…

【INTEL(ALTERA)】带有浮点单元 (FPU) Nios® V/g 处理器在 英特尔® Cyclone10 GX 设备中执行不正确的浮点运算

说明 由于 英特尔 Quartus Prime Pro Edition 软件版本 23.3 存在一个问题&#xff0c;当使用 Nios V/g 处理器并在 英特尔 Cyclone 10 GX 设备中启用 FPU 时&#xff0c;浮点运算无法按预期进行。 Nios V/g 处理器 – 启用浮点单元 解决方法 请勿在 英特尔 CycloneNios 10 G…

鸿蒙开发(ArkUI)—分析DatePicker组件

一、操作环境 操作系统: Windows 10 专业版、IDE:DevEco Studio 3.1、SDK:HarmonyOS 3.1 二、DatePicker组件 日期选择器组件&#xff0c;用于根据指定日期范围创建日期滑动选择器。 子组件 无。 接口 DatePicker(options?: {start?: Date, end?: Date, selected?: …

分析HarmonyOS应用/服务的CPU活动性能

CPU Profiler 性能分析是用来分析CPU性能瓶颈的工具&#xff0c;可以实时查看应用/服务的CPU使用率和线程活动&#xff0c;也可以查看记录的方法跟踪数据、方法采样数据和系统跟踪数据的详情。基于CPU性能分析&#xff0c;您可以了解在一段时间内执行了哪些方法&#xff0c;以及…

你有一份企业法务建设方案待查收

在当今这个信息化飞速发展的时代&#xff0c;企业法务工作的高效与精准已成为企业竞争力的重要组成部分。随着商业活动的日益复杂和法律法规的不断更新&#xff0c;传统的法务管理模式已难以满足现代企业的需求。因此&#xff0c;企业法务信息化建设不仅是时代发展的必然趋势&a…

NC248:左叶子之和(C++)

1.题目描述 2.题目分析 我们以一个二叉树为例 左叶子的特点是什么&#xff1f; 是左节点并且没有左右孩子节点 所以我们用leftnode保存root->lefe节点&#xff0c;判断条件为leftnode存在&#xff0c;并且不存在leftnode->left和leftnode->right&#xff0c;如果满…

实战项目(二)汽车保养管家系统

一、实现技术 前端技术&#xff1a;html、javascript(jquery、ajax、json)、css、layui 后端技术&#xff1a;java、mysql、servlet 开发工具&#xff1a;eclipse、vscode 二、项目描述 基于web的汽车保养管家系统的设计与实现 一、功能需求 1&#xff0e;用户功能 1.1…

UI动效如何通过ps放到贴图模板里导出gif效果图

经常看到设计网站上有将UI动效在好看的模板里进行展示的&#xff0c;效果非常棒&#xff01;很多设计师应该都可以做出好看的UI动效动画效果&#xff0c;但不知道怎么把动效放到手机模板里进行更好的展示。 这篇教程就是帮你把制作好的动效动画通过ps放到好看的模板里&#xf…

责任链模式在java中的实现

1 总览 2 概念 避免请求发送者与接收者耦合在一起&#xff0c;让多个对象都有可能接收请求&#xff0c;将这些对象连接成一条链&#xff0c;并且沿着这条链传递请求&#xff0c;直到有对象处理它为止。职责链模式是一种对象行为型模式。 3 实现 公共部分&#xff0c;一个系…

【数据结构 03】循环队列

一、原理 循环队列从功能角度具有队列的性质&#xff0c;即遵从先进先出原则&#xff0c;但是其存储方式是顺序存储。 循环队列的存储空间大小通常都是固定的&#xff0c;通过前指针和尾指针的移动控制循环队列数据的增删。 特征&#xff1a;顺序存储、先进先出、容量有限&a…

超声波风速风向传感器的优势及应用

TH-WQX2随着科技的不断发展&#xff0c;传感器技术日益成为众多领域中不可或缺的一部分。其中&#xff0c;超声波风速风向传感器凭借其独特的优势&#xff0c;在气象、能源、环保等领域中发挥着越来越重要的作用。 首先&#xff0c;超声波风速风向传感器无需机械转动部件&#…

代码随想录算法刷题训练营day20

代码随想录算法刷题训练营day20&#xff1a;LeetCode(654)最大二叉树、LeetCode(617)合并二叉树、LeetCode(700)二叉搜索树中的搜索、LeetCode(700)二叉搜索树中的搜索、LeetCode(98)验证二叉搜索 LeetCode(654)最大二叉树 题目 代码 import java.util.Arrays;/*** Definit…

vue3.0 + 动态加载组件 + 全局注册组件

首先 vue 动态加载组件使用的是 component 标签&#xff0c;并通过设置组件的is 属性来指定要渲染的组件。例如&#xff1a; <component :is"currentComponent"></component>其中&#xff0c;currentComponent 是一个变量&#xff0c;它的值可以是以下几…

搭建幻兽帕鲁需要什么样的服务器

作为一个开放世界生存制造类游戏《幻兽帕鲁》收获了空前绝后的热度&#xff0c;玩家们在游戏中通过在地图上捕捉收集到的“帕鲁”进行训练&#xff0c;合理利用他们的能力进行战斗&#xff0c;建立自己的家园、开辟新的世界、解锁新的冒险情节&#xff0c;获取更多游戏信息增加…

【数据结构 01】栈

一、原理 栈通常从数据结构和内存空间两个角度解释&#xff0c;从数据结构的角度&#xff0c;栈是一种线性结构表&#xff0c;只允许在固定的一端进行插入和删除元素&#xff0c;从内存空间角度&#xff0c;操作系统为函数和变量分配的内存空间通常在栈区&#xff0c;但是无论…