哈希思想的应用:位图、布隆过滤器及哈希切割

news2024/9/22 21:20:59

一.位图引入

给40定亿个不重复的无符号整数存储在文件中,如何判断一个数在不在其中?

分析:最容易想到的思路是将这些数字存储到某个能够实现快速查找的容器中,如红黑树或哈希表。

但是,10亿个字节大约占1G内存,那么40亿个整数如果想要在内存中存储需要16G空间。

故使用set(红黑树)或unordered_set(哈希表)等容器来存储是不现实的,主要原因就是内存不够。 

对于这种判断在不在的问题,不需要将数字直存储起来,而是用数字映射一个比特位,该bit标识在与不在两种状态即可。这样的数据结构,就叫做位图。

二.位图实现

  1. 非类型模版参数N,表示你想要开多少个bit的位图,这取决于数据的范围。如果数据是100以内的正整数,只需要开100bit;如果数据是unsigned int,需要开2^32个bit(512M内存)
  2. 数字的映射方法(哈希方法):直接定址法,数字x映射第x个比特位
  3. set接口:将x对应的比特位置为1,标识x在位图中
  4. reset接口:将x对应的比特位置为0,标识x不在位图中
  5. test接口:判断x是否在位图中,只需判断x对应的比特位是0还是1
template<size_t N>
	class bitset
	{
	private:
		vector<char> _bits;
	public:
		bitset()
			: _bits(N/8+1, 0)
		{}

		void set(size_t x)
		{
			size_t i = x / 8;
			size_t j = x % 8;
			//将_bits[i]的第j位置为1
			_bits[i] |= (1 << j);
		}

		void reset(size_t x)
		{
			size_t i = x / 8;
			size_t j = x % 8;
			//将_bits[i]的第j位置为0
			_bits[i] &= ~(1 << j);
		}

		bool test(size_t x)
		{
			size_t i = x / 8;
			size_t j = x % 8;
			if (_bits[i] & (1 << j))
			{
				return true;
			}
			return false;
		}
	};

三.位图和哈希表的比较 

  1. 相同点:二者都应用了哈希的思想,即用一个值映射另一个值的思想。二者都用值映射存储位置,只不过哈希表映射的每个存储单元较大,可以存储各种各样类型的数据,但位图映射的每个存储单元只有一个bit,存储的信息非常有限
  2. 不同点:哈希表将值存入了映射的存储空间,而位图没有。故哈希表可以处理哈希冲突(线性探测法或哈希桶),这是因为搜索时可以用值去和存储单元中的值进行比较。而位图不能发生哈希冲突,一个bit只能对应一个值,这也是我们采用直接定址法来映射的原因。

四.位图的应用 

1.给定100亿个整数,找只出现一次的整数

与开头的题目类似,不过这里要想办法表示一个数的三种状态,即出现0次,出现1次,出现2次以上,这需要两个bit才能标识。故可以使用两个位图。

00:第一个位图中x对应的bit位0,第二个位图中x对应的bit为0,表示x出现0次

01:第一个位图中x对应的bit位0,第二个位图中x对应的bit为1,表示x出现1次

10:第一个位图中x对应的bit位1,第二个位图中x对应的bit为0,表示x出现2次及以上

template<size_t N>
	class twobitset
	{
	private:
		bitset<N> _bs1;
		bitset<N> _bs2;
	public:
		void set(size_t x)
		{
			if (_bs1.test(x) == false && _bs2.test(x) == false)
			{
				//00->01
				bs2.test(x);
			}
			else if (_bs1.test(x) && _bs2.test(x) == true)
			{
				//01->10;
				bs1.set(x);
				bs2.reset(x);
			}
		}
        bool test(size_t x)
		{
			//01->出现1次
			return bs2.test(x) == true;
		}
	};

2.给两个文件,分别有100亿个整数,如何找两个文件的交集--交集中没有没有重复数字

方法一:分别将两个文件的数字放进两个位图,然后遍历每一个比特位,如果该比特位都是1,则对应的数字是交集 

方法二:先将一个文件中的数字放进位图,然后遍历另一个文件,看数字在不在位图中,如果在,则该数字是交集,同时将该数字对应的比特位置0,防止重复。

当文件中的数字个数大于2^32时,遍历比特位的方法更优,否则遍历文件的方法更优

五.布隆过滤器 

位图的缺点是只能只能映射整型,遇到字符串或者其它自定义类型无能为力。哈希表可以映射字符串,它是怎么做的呢?哈希表是先将字符串映射整型,然后再映射存储位置,做了两次哈希。

位图是否可以借鉴这种思路呢?答案是不可以,因为字符串无穷无尽,而整型是有限的,不管你怎么优化哈希方法,一定会存在多个字符串对应一个整型的情况,即使你再采用直接定址法照样会产生哈希冲突,而前面说了由于位图中没有把值存下来,所以不允许哈希冲突,否则判断时可能会出错。

而布隆过滤器直接另辟蹊径,既然哈希冲突可能会使判断出错,那么总有判断结果准确的时候,我不要求判断结果百分百正确,但是我可以想办法降低判断出错的概率。这就是布隆过滤器,它适用于允许判断出错的场景。

那么布隆过滤器是怎么降低出错概率的呢?

以string为例,布隆过滤器采用多种不同的哈希方法,将一string映射出多个整型值,然后再用整型值映射比特位(除留取余法)。这样一来,一个string对应多个bit,只有这几个bit都是1的时候才给出结论;string在位图中,当有一个bit为0时,给出string不在位图中的结论。

具体用几个哈希方法?哈希函数越多,映射的位就越多,误判率越低,但需要的空间就越多。这里直接给出结论,3个哈希函数比较合适,具体的数学推导有兴趣自行研究。

  1. 当给出在位图中的结论时,字符串不一定在位图中,因为这几个bit不一定是该字符串映射的。
  2. 当给出不在位图中的结论时,字符串一定不在位图中,因为没有字符串映射这几个bit

以上两点就是布隆过滤器的核心逻辑,也就是说,判断值不在,这个结论一定是准确的。因此,布隆过滤器才能发挥它的“过滤”功能。

template<size_t N, class K, class Hash1, class Hash2, class Hash3>
	class BloomFilter
	{
	private:
		bitset _bs;
		Hash1 _hf1;
		Hash2 _hf2;
		Hash3 _hf3;
	public:
		void set(const K& key)
		{
			size_t hash1 = _hf1(key) % N;
			size_t hash2 = _hf2(key) % N;
			size_t hash3 = _hf3(key) % N;

			_bs.set(hash1);
			_bs.set(hash2);
			_bs.set(hash3);
		}
		
		bool test(const K& key)
		{
			size_t hash1 = _hf1(key) % N;
			size_t hash2 = _hf2(key) % N;
			size_t hash3 = _hf3(key) % N;
			return _bs.test(hash1) 
				&& _bs.test(hash2) 
				&& _bs.test(hash3);
		}
	};

六.布隆过滤器的应用

一个典型的例子,当用户注册账号时,需要填写昵称。

可以先将数据库中的所有昵称放进布隆过滤器中,当用户输入昵称时,快速在布隆过滤器中查找昵称是否已经存在。若给出的结果是不在,则一定不在,提示用户昵称可用;若给出的结果是不在,则不能确定在不在,此时再去数据库中查找,以该结果为准,提示用户昵称是否可用。

布隆过滤器的意义在于过滤掉了那些一定不在数据库中的昵称,这样可以减少访问数据库的次数,从而提升效率。

七.布隆过滤器的删除

布隆过滤器原则上是不支持删除的,将一个字符串对应的多个比特位置0,可能会影响其它字符串。

 如图,删除str2就影响了str1,所以布隆过滤器原则上是不支持删除的。

如果想要支持删除,就要使用引用计数的方法,每一个存储单元不再只用一个bit来标识在与不在两种状态,而是用若干个bit作为一个存储单元,标识有多少个值映射到该存储单元。

例如三个bit作为一个存储单元,则该存储单元最多有8个值能映射,set就加1,reset就减1。当判断某个值在不在,只需判断它映射的若干个存储单元是否都不为0即可。

八. 哈希切割

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

近似算法:先将一个文件中的字符串放进布隆过滤器,布隆过滤器越大越好,这样可以减少哈希冲突。既然有1G空间,我们直接开2^33个bit(要用unsigned long long表示)。然后我们再遍历另一个文件,逐一判断字符串是否在布隆过滤器中。

精确算法怎么实现呢?位图只能放整型数据,哈希表,红黑树等又存不下。对此,我们的思路是分块处理,逐个击破,将文件分成若干个小份,不就可以加载进内存了吗?

如何分?平均分吗?分了等于没分,要找交集还是得遍历文件。

这里的问题在于要使的相同的字符串进入同一个文件,于是哈希思想的又一重大应用——哈希切割闪亮登场。

100亿个字符串,假定每个字符串50byte,则有500G大小。我们预计将每个大文件分为500个小文件,于是给每个文件编号:A0,A1,A2……A499,B0,B1,B2……B499。

分别遍历两个文件,所有字符串都用某个固定的哈希方法,映射成整型值,然后模上500,结果是多少,就存入几号文件。

相同的字符串映射出的整型值相同,因此会进入同一个文件。这样,我们就能对每个小文件单独处理了。例如将A0文件的字符串全部加载到内存,哈希表存储,遍历B0文件,逐一判断每个字符串是否在哈希表中。

这里还有一个细节,由于不是平均切割,某个文件可能会特别大,有两种情况。第一,可能是有很多重复的字符串,这并不影响,因为哈希表有去重功能,不会把这些重复的都存进去。第二,可能是有很多相似的字符串经过某种哈希方法映射到了相同文件,这时需要更换哈希方法,将该小文件再次切分成更小的文件。

综上,一开始可以不用考虑这个细节,直接读取文件存进哈希表,当内存不够时会抛出异常,我们只需捕获异常,然后再切分文件。

再看一个问题:

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

解决方案:

哈希切割成若干个小文件,依次处理每个小文件,使用一个unordered_map/map统计ip出现的次数。

如果统计过程中出现抛内存异常,则说明单个文件过大,冲突太多,更换哈希函数,再次哈希切割这个小文件

如果没有抛异常,则正常统计,统计完一个小文件,记录最大的,释放内存,再统计下一个小文件。

如果要求次数最多的ip,则去记录的所有最大值中的最大值。如果要求top K,则建立K个元素的小堆,遍历记录的值来更新堆。最终留下的就是top K。

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

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

相关文章

使用JDBC操作数据库时,插入数据中文乱码

如图&#xff1a; 解决办法&#xff1a; 修改连接数据库的路径&#xff0c;即url 如下&#xff1a; 设置编码格式为utf-8 urljdbc:mysql://localhost:3306/qfedu?useUnicodetrue&characterEncodingUTF-8再次运行&#xff0c;插入数据即可

python pytorch实现RNN,LSTM,GRU,文本情感分类

python pytorch实现RNN,LSTM&#xff0c;GRU&#xff0c;文本情感分类 数据集格式&#xff1a; 有需要的可以联系我 实现步骤就是&#xff1a; 1.先对句子进行分词并构建词表 2.生成word2id 3.构建模型 4.训练模型 5.测试模型 代码如下&#xff1a; import pandas as pd im…

Linux多线程同步

Linux多线程同步 1、线程同步的概念1.1 为什么要同步1.2 同步方式 2、互斥锁2.1 互斥锁函数2.2 互斥锁使用 3、死锁4、读写锁4.1 读写锁函数4.2 读写锁使用 5、条件变量5.1 条件变量函数5.2 生产者和消费者 6、信号量6.1 信号量函数6.2 生产者和消费者6.3 信号量的使用6.3.1 总…

Android系统源码中添加可编译运行执行程序,java

文章目录 Android系统源码中添加可编译运行执行程序&#xff0c;java1.Android设备中执行编译运行java代码2.编译执行jar包 Android系统源码中添加可编译运行执行程序&#xff0c;java 1.Android设备中执行编译运行java代码 新建一个文件夹&#xff0c;以及Java类的包路径 测…

AI 编程如何助力开发者高效完成架构设计工作?

▼最近直播超级多&#xff0c;预约保你有收获 今晚直播&#xff1a;《AI 编程技术架构剖析和案例开发实战》 —1— AI 编程能帮我们完成哪些工作&#xff1f; 从目前企业级种种现实场景应用来看&#xff0c;AI 编程已经成为一种帮助开发者解决架构设计复杂问题、提高编程效率以…

C/C++转义符:\x

文章目录 什么是转义符使用"\x"定义char数组宏定义中的\ 什么是转义符 在C语言中&#xff0c;转义符用于将一些特殊字符表示为单个字符&#xff0c;常用的转义符有&#xff1a; \\&#xff1a;反斜杠符号\&#xff1a;单引号\"&#xff1a;双引号\a&#xff1…

力扣 --- 删除有序数组中的重复项 II

题目描述&#xff1a; 给你一个有序数组 nums &#xff0c;请你 原地 删除重复出现的元素&#xff0c;使得出现次数超过两次的元素只出现两次 &#xff0c;返回删除后数组的新长度。 不要使用额外的数组空间&#xff0c;你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的…

考试复习

选择20道 填空10道 判断10道 简答4-5道 编程题2道 一、选择题 1.js中更改一个input框的值&#xff1a; <input ida type"text" value"123456"> 通过a.value改变他的值 方法&#xff1a; 在script标签中通过id获得该输入框对象&#xff0c;然…

记录一次爱快路由ACL策略引起的大坑

环境&#xff1a; A公司和B公司采用爱快的ipsec互联 B公司同时有加密软件限制网络 问题&#xff1a;对方ERP无法连接我们的数据库服务器 先简单测试了下1433端口是不是通的 下面的测试结果&#xff0c;直接ping是通的&#xff0c;但是加上1433端口后就不通 排查过程&#xff1…

高等数学上岸宝典笔记

①不单调的函数也可能有反函数 ②注意反函数与函数转换时的定义域与值域 ③收敛数列不一定有最值 收敛数列必有上界和下界&#xff0c;但不一定有最值&#xff0c;比如{An}1/n&#xff0c;下界为0&#xff0c;但永远取不到0 ④数列与其子数列的关系 例题&#xff1a; ⑤带根号…

道路病害检测数据集RDD2022的标签映射关系【参考自官网给出的label_map.pbtxt文件,附查看代码】

TOC 结论 Label ID: 1, Label Name: D00 Label ID: 2, Label Name: D10 Label ID: 3, Label Name: D20 Label ID: 4, Label Name: D40链接地址 https://github.com/sekilab/RoadDamageDetector/ 查看代码 # 打开 label_map.pbtxt 文件 def read_label_map(file_path):label…

模拟算法【2】

文章目录 &#x1f958;6. N 字形变换&#x1f372;题目&#x1fad5;算法原理&#x1f963;代码实现 &#x1f957;38. 外观数列&#x1f37f;题目&#x1f9c2;算法原理&#x1f9c8;代码实现 &#x1f958;6. N 字形变换 &#x1f372;题目 题目链接&#xff1a;6. N 字形变…

【linux网络】补充网关服务器搭建,综合应用SNAT、DNAT转换,dhcp分配、dns分离解析,nfs网络共享以及ssh免密登录

目录 linux网络的综合应用 1&#xff09;网关服务器&#xff1a;ens35&#xff1a;12.0.0.254/24&#xff0c;ens33&#xff1a;192.168.100.254/24&#xff1b;Server1&#xff1a;192.168.100.101/24&#xff1b;PC1和server2&#xff1a;自动获取IP&#xff1b;交换机无需…

python中的字符串

字符串 字符串是编程语言中的一种基本数据类型&#xff0c;用于表示一串字符序列。在Python中&#xff0c;字符串是不可变的&#xff0c;也就是说一旦字符串被创建&#xff0c;就无法修改其中的字符。 Python中的字符串可以用单引号或双引号括起来&#xff0c;例如&#xff1…

手机电脑同步的时间管理工具

有不少上班族会发现自己有太多的工作要完成&#xff0c;并且在工作中往往会浪费很多时间在无关紧要的事情上&#xff0c;而不是专注于真正重要的任务&#xff0c;因此没有足够的时间来完成所有任务。在这种情况下&#xff0c;我们可以使用时间管理软件来帮助自己优先考虑重要的…

Android 架构实战MVI进阶

MVI架构的原理和流程 MVI架构是一种基于响应式编程的架构模式&#xff0c;它将应用程序分为四个核心组件&#xff1a;模型&#xff08;Model&#xff09;、视图&#xff08;View&#xff09;、意图&#xff08;Intent&#xff09;和状态&#xff08;State&#xff09;。 原理&…

Mybatisplus同时向两张表里插入数据[事务的一致性]

一、需求&#xff1a;把靶器官的数据&#xff0c;单独拿出来作为一个从表&#xff0c;以List的方式接收这段数据&#xff1b; 此时分析&#xff0c;是需要有两个实体的&#xff0c;一个是主表的实体&#xff0c;一个是从表的实体&#xff0c;并在主表实体新增一个List 字段来接…

免费WordPress站群插件-批量管理站群的免费软件

WordPress站群插件&#xff1a;让文章管理如丝般顺滑 在众多网站建设工具中&#xff0c;WordPress一直以其简便易用、丰富的插件生态而备受青睐。对于站群管理者而言&#xff0c;如何高效地更新、发布和推送文章是一项不可忽视的任务。本文将专注分享一款WordPress站群插件&am…

Rust的Vec优化

本篇是对Rust编程语言17_Rust的Vec优化[1]学习与记录 MiniVec https://crates.io/crates/minivec enum DataWithVec { // tag,uint64,8字节 I32(i32), // 4字节,但需内存对齐到8字节? F64(f64), // 8字节 Bytes(Vec<u8>), // 24字节}fn main()…

watch函数与watchEffect函数

watach函数&#xff1a; 与vue2.x的配置功能一致 监视指定的一个或多个响应式数据, 一旦数据变化, 就自动执行监视回调 默认初始时不执行回调, 但可以通过配置immediate为true, 来指定初始时立即执行第一次 通过配置deep为true, 来指定深度监视 watchEffect函数&#xff1a;…