C++进阶篇5---番外-位图和布隆过滤器

哈希的应用

一、位图

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

看到查找元素的范围,暴力肯定是过不了的,我们要么二分要么哈希,但是二分要求排序,题目说没排过序,只剩下哈希,但是如果用正常的哈希表肯定不行,数据量太大了(可以算一下,大概15G),根本加载不进内存,更别谈放到哈希表中了,那怎么办? 

这时候就需要用到位图---本质就是状态压缩版的哈希表,用一个比特位表示一个数字,大大压缩了数据量,(整形是4字节,如果是哈希表只能用来表示一个数字,但是位图可以用来表示4*8=32个数),数据量缩小了32倍,大概0.5G,具体的实现如下

namespace zxws
{template <size_t N=100>class bitset{public:bitset(){bit.resize(N/32+1);}void set(size_t x)//增{size_t i = x / 32;size_t j = x % 32;bit[i] |= (1u << j);//1u代表unsigned int类型的1}void reset(size_t x)//删{size_t i = x / 32;size_t j = x % 32;bit[i] &= ~(1u << j);}bool test(size_t x)//查{size_t i = x / 32;size_t j = x % 32;return (bit[i] >> j) & 1u;}private:vector<int>bit;};
}

模拟实现没啥难度,就是要了解位运算,当然这只是位图的最重要的几个函数,还有一些其他的不常用的就不模拟实现了,有兴趣大家可以去查看文档

那么了解了位图的实现原理,我们再来看看下面的几个题

1. 给定100亿个整数,设计算法找到只出现一次的整数?
2. 给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件交集?
3. 位图应用变形:1个文件有100亿个int,1G内存,设计算法找到出现次数不超过2次的所有整数
题1:正常用一个位图,不好做,因为一个数字对应一个比特位,而一个比特位只有0 / 1两个状态,无法表示没出现,出现1次和出现多次这3个状态,那怎么办?既然一个比特位无法表示,那两个比特位呢?共有00,01,10,11四个状态,绰绰有余,实现如下
namespace zxws
{template <size_t N = 100>class twobitset{public:void set(size_t x){size_t i = x / 32;size_t j = x % 32;if (bs1.test(x) == false && bs2.test(x) == false)//00->01{bs1.set(x);}else if (bs1.test(x) == true && bs2.test(x) == false)//01->10{bs1.reset(x);bs2.set(x);}}void test(size_t x){return bs1.test(x) == true && bs2.test(x) == false;//01--代表只出现一次}private:bitset<N>bs1;bitset<N>bs2;};
}

题2:找文件交集,这个就很明显了,两个位图分别存放两个文件中的数字,然后比特位之间&一下,比特位上为1的就是交集

题3:这题其实和第1题一样,都是查看数字出现次数,要求不出现两次,即有没出现,出现1次,出现2次和出现2次以上四个状态,两个位图正好够了,实现同题1

二、布隆过滤器

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

实现原理图

一般来说用三个哈希函数就差不多了

上图是网上的研究数据显示结果,仅供参考(k,m,n满足上诉关系时,不容易发生哈希冲突)

布隆过滤器的作用范围还是很广泛的,尤其是在不怎么关心某一个东西是否真的存在的场景下,举个例子,比如说取用户ID,当你取的id没人用时,OK你创建成功,当你取的id显示有人用时,如果是真的有人用了,那我们就换一个,如果没人用,它误判了,那我们也就是不能用这个id而已,没有啥太大影响,这时布隆过滤器就非常合适

当然如果说用户投诉说明明没人用这个id,却不让用,要求我们修复bug,这时我们只要让在布隆过滤器过滤后显示为存在的数据再去数据库中校验一下即可,

当然也有人会觉得反正都要去数据库校验还要布隆过滤器干嘛,注意:1.布隆过滤器它为啥叫过滤器,关键就是它只能确定不存在的数据,不能确定存在的数据。2.网络上通讯会比较耗时,如果每一个id的确认都需要与服务器上的数据库校验,就会浪费时间

实现如下

//哈希函数就自行去网上找哪些不容易产生哈希冲突的就行
template <size_t N, class K=string, class HashFunc1=HashFun<K>, class HashFunc2=DGBHash<K>, class HashFunc3=APHash<K> >
class BloomFiler {
public:void set(const K& key){size_t hash1 = HashFunc1()(key) % N;size_t hash2 = HashFunc2()(key) % N;size_t hash3 = HashFunc3()(key) % N;_bs.set(hash1);_bs.set(hash2);_bs.set(hash3);}bool test(const K& key){size_t hash1 = HashFunc1()(key) % N;size_t hash2 = HashFunc2()(key) % N;size_t hash3 = HashFunc3()(key) % N;if (_bs.test(hash1) == false|| _bs.test(hash2) == false|| _bs.test(hash3) == false)return false;return true;}
private:bitset<N*5>_bs;
};

两个问题:

1. 给两个文件,分别有100亿个query,我们只有1G内存,如何找到两个文件交集?分别给出精确算法和近似算法(具体下面一个专题讲)

2. 如何扩展BloomFilter使得它支持删除元素的操作?一般来说是不能支持的,因为删除一个元素的映射会影响其他元素的哈希映射(因为它们会出现冲突),但是我们可以给它们加一个引用计数,这样就能在删除它的同时不影响其他元素的映射

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

三、哈希分割---哈希思想的扩展

给一个超过100G大小的log file, log中存着IP地址, 设计算法找到出现次数最多的IP地址?如何找到top K的IP?

100G的file很显然太大,我们的想法是将它分割成一个个小文件,然后在小文件中计数,我们将文件按Hash(id) % 100,得到100个1G的小文件(理想情况),然后我们就可以在小文件中统计每个id出现的次数(因为同一个id经过哈希映射会在同一个小文件中),但是,上面的只是理想情况,如果某一个小文件的大小为10G,也就是分完之后还是太大了,我们又该怎么办?

出现上诉情况共分两种可能:

1.相同的id太多
2.哈希冲突太多,导致多个不同的id都放在了同一个小文件中

如果是情况一,我们不用管,map中只会插入一次这个id,空间足够

如果是情况二,会报内存错误,这时我们就对这个小文件进行二次哈希分割即可

top K问题用堆实现就行,之前再二叉树数据结构中讲过的


下面,我们回过头去看看

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

近似算法就是用布隆过滤器,但是精确的算法呢?

这个query的大小也要考虑到,假设query的大小为50字节,那么一共5000亿字节,约等于500G,很明显了哈希切割,当然我们得先将query转成整数,Hash(query)%500,两个文件各自分成500个1G的小文件(理想情况),这样两个文件中相同的query会分别放在同一个余数的两个小文件中,如下图

当然它也会出现小文件太大的情况,处理方法同上,注意这个不能用位图的原因是query里面存的不一定是整数,这样不同的query查询也有可能映射到用一个比特位(这也是布隆过滤器不准确的原因之一),就不精确了


如果上诉内容对你理解哈希有帮助的话,不要忘记点赞+评论哟!!!

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

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

相关文章

自学成为android framework工程师需要准备哪些装备-千里马车载车机系统开发学习

背景 hi&#xff0c;粉丝朋友们&#xff1a; 大家好&#xff01;经常有很多学员买课同学都会问到需要准备哪些装备&#xff0c;我也回答了很多学员了&#xff0c;今天就搞一篇文章来统一说明一下&#xff0c;告诉一下大家如果你想从一个framework新手变成一个framework开发的高…

sprintboot快速初始化【Springboot】

1.首先选择创建项目 2.填写对应的项目信息 一定要勾选maven&#xff0c;否则没有pom文件&#xff0c;选择next 3.选择应用场景 点击 create&#xff0c;DIEA就会根据你的选择自动创建项目骨架&#xff1b; 4.创建一个控制层 随便创建一个控制层&#xff0c;测试一下项目是否…

链接1:编译器驱动程序

文章目录 GNU编译器示例编译 GNU编译器 GNU编译器&#xff08;GNU Compiler&#xff09;是由自由软件基金会&#xff08;Free Software Foundation&#xff0c;FSF&#xff09;开发和维护的一套编译器集合。这些编译器主要用于编译各种编程语言的源代码&#xff0c;将其转换为…

【前端开发】Next.js VS Nest.js–Nest和Next之间的区别

web开发框架起到支持作用&#xff0c;并提供了开发应用程序的有效方法。它具有不同的功能和特性&#xff0c;这使得开发过程更容易&#xff0c;开发人员总是能找到最快的编码方式。这就是为什么&#xff0c;无论是后端还是前端框架&#xff0c;大多数开发人员都喜欢使用它。 一…

深度盘点:100 个 Python 数据分析函数总结

经过一段时间的整理&#xff0c;本期将分享我认为比较常用的100个实用函数&#xff0c;这些函数大致可以分为六类&#xff0c;分别是统计汇总函数、数据清洗函数、数据筛选、绘图与元素级运算函数、时间序列函数和其他函数。 技术交流 技术要学会交流、分享&#xff0c;不建议…

10 动态规划分割回文串III

来源&#xff1a;LeetCode第1278题 难度&#xff1a;苦难 描述&#xff1a;给你一个由小写字母组成的字符串s&#xff0c;和一个整数k&#xff0c;请按照下面的要求分割字符串:首先&#xff0c;你可以将s中的部分字符修改为其他的小写字母。接着你需要把s分割成k个非空不想交…

【Redis实现全局唯一ID】

一、全局唯一ID的需求产生。 在订单业务中&#xff0c;我们需要保证id是绝对唯一的。 使用数据库自增长的id在分布式的情况下把表做了拆分处理后有可能会出现id重复的情况&#xff0c;这就违背了唯一性。而且数据自增长的id有很强的规律性&#xff0c;可以根据id推断出订单的数…

C语言中的static的用法(超级详细)

&#x1f339;作者:云小逸 &#x1f4dd;个人主页:[云小逸的主页] &#x1f4dd;码云&#xff1a;云小逸 (YunXiaoYi003) &#x1f91f;motto:要敢于一个人默默的面对自己&#xff0c;强大自己才是核心。不要等到什么都没有了&#xff0c;才下定决心去做。种一颗树&#xff0c;…

大型网站系统架构演化(Web)

大型网站系统架构演化 大型网站系统架构演化需要关注的维度涉及的技术演进过程单体架构垂直架构使用缓存改善网站性能缓存与数据库的数据一致性问题缓存技术对比Redis分布式存储方案Redis集群切片的常见方式Redis数据类型Redis 淘汰算法 大型网站系统架构演化 需要关注的维度 …

【深入解析git和gdb:版本控制与调试利器的终极指南】

【本节目标】 1. 掌握简单gdb使用于调试 2. 学习 git 命令行的简单操作, 能够将代码上传到 Github 上 1.Linux调试器-gdb使用 1.1.背景 程序的发布方式有两种&#xff0c;debug模式和release模式release模式不可被调试&#xff0c;debug模式可被调试Linux gcc/g出来的二进制…

【Cmake】Cmake基础学习

CMake学习 一、基础学习 1. 利用Cmake进行单个源代码构建可执行文件 (1)基础命令 最基本的 CMake项目是由单个源代码文件构建的可执行文件。对于这样的简单项目,只需要一个包含三个命令的 CMakeLists.txt 文件。 注意: 虽然 CMake 支持大写、小写和混合大小写命令,但是…

Java中的mysql——面试题+答案(存储过程,外键,隔离级别,性能优化)——第23期

当涉及MySQL时&#xff0c;面试题的范围可以涵盖数据库设计、优化、复制、分片等方面。 什么是数据库范式&#xff1f;为什么要遵循数据库范式&#xff1f; 答案&#xff1a; 数据库范式是一组规范&#xff0c;用于设计关系数据库表的结构&#xff0c;以减少数据冗余和提高数据…

Spring事件注解@EventListener【观察】

一、背景 在开发工作中&#xff0c;我们常常会遇到这样一种情况&#xff1a;完成一项任务后&#xff0c;需要向其他模块广播消息或通知&#xff0c;以触发其他事件的处理。逐个发送请求固然可行&#xff0c;但更好的方式是采用事件监听&#xff0c;它是设计模式中的发布-订阅模…

Unity 关于生命周期函数的一些认识

Unity 生命周期函数主要有以下一些&#xff1a; Awake(): 在脚本被加载时调用。用于初始化对象的状态和引用。 OnEnable(): 在脚本组件被启用时调用。在脚本组件被激活时执行一次&#xff0c;以及在脚本组件被重新激活时执行。 Reset(): 在脚本组件被重置时调用。用于重置脚本…

微信小程序Vue+nodejs教室自习室座位预约系统68u2m

本文从管理员、用户的功能要求出发&#xff0c;教室预约系统小程序中的功能模块主要是实现管理端&#xff1b;首页、个人中心、教室信息管理、教室设备管理、用户管理、教室预约管理、管理员管理、系统管理&#xff0c;微信端&#xff1b;首页、教室信息、教室设备、教室预约、…

西南科技大学信号与系统A实验二(信号频谱分析)

一、实验目的 1.掌握用 matlab 软件绘制信号频谱的方法; 2.进一步理解抽样定理; 3.理解傅里叶变换的性质(频移特性). 二、实验原理 (一)fft 函数的调用 matlab 提供 fft 函数来计算信号 x(n)的快速离散傅里叶变换 (FFT). z 格式:y=fft(x) 计算信号 x 的快速离散傅里叶…

链路追踪详解(二):链路追踪技术的演进

目录 早期的链路追踪 分布式系统的挑战 链路追踪技术的诞生 链路追踪系统的开源和标准化 微服务架构对链路追踪技术的促进 云原生时代下的链路追踪技术 链路追踪技术未来的趋势 小结 链路追踪的发展历程可以追溯到几十年前&#xff0c;随着互联网技术的不断发展&#x…

【Rust】结构体与枚举

结构体struct 基础用法 一个存储用户账号信息的结构体&#xff1a; struct User {active: bool,username: String,email: String,sign_in_count: u64, }声明一个User的实例变量&#xff1a; struct User {active: bool,username: String,email: String,sign_in_count: u64,…

若依vue-修改标题和图标

因为我们拉下来的代码,图标和logo是若依的,这和我们需要做出来的效果有差别 这个时候就需要去对应的文件内去修改标题和图标 (主要就是这两个地方的图标和标题) 修改菜单里面的logo以及文字 修改文字 位置: src/layout/component/Sidebar/Logo.vue 此处的title文字是定义在…

ESXi vSAN 整合多主机磁盘

VSAN 与 RAID区别&#xff1a; vSAN 可以管理 ESXi 主机&#xff0c;且只能与 ESXi 主机配合使用。一个 vSAN 实例仅支持一个群集。vSAN 不需要外部网络存储来远程存储虚拟机文件&#xff0c;例如光纤通道 (FC) 或存储区域网络 (SAN) 使用传统存储&#xff0c;存储管理员可以…