【C++】哈希(位图,布隆过滤器)

news2024/11/8 23:51:38

今天的内容是哈希的应用:位图和布隆过滤器a31ac5e10a734ee6a6b4ec5d54248eae.gif


目录

一、位图

1.位图概念

2.位图的应用

二、哈希切分

三、布隆过滤器

1.布隆过滤器的概念

2.布隆过滤器的应用

四、总结


 

一、位图

1.位图概念

今天的内容从一道面试题开始引入:

给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在
这40亿个数中。
 
首先我们对40亿个无符号整数改变一下,它到底是多少G呢?
 
40亿个整数大概是  40亿*4个字节=160亿个字节
 
4G=2^32byte,大概为42亿九千万字节,所以1G大概就是10亿字节 ,所以40亿个整数大概就是16G,那这么大数据放到内存中肯定是放不下的,所以什么二分查找,什么map,set更何况还有额外的消耗,这更不可能完成了,于是我们可以利用哈希的思想来搞一个位图!
 
        判断数据是否在给定的整形数据中,结果是在或者不在,刚好是两种状态,那么可以使用一个二进制比特位来代表数据是否存在的信息,如果二进制比特位为1,代表存在,为0
代表不存在。
 

位图是一种直接定址法的哈希,因此效率很高,用O(1)就可以探测到对应位是0还是1,效率非常高,因此可以快速判断。

 

利用每一个比特位的0或1的情况,来判断数在不在,所以40亿不重复的数,开辟2^32-1个比特位,转化为G,也就512m,内存很小。

ea7d7fb0a5e846aab184583d7bddf21d.png

举例说明:

 

4dd4a283419944949cb5d6e616912a9d.png 每个数我们可以先num/8,算出他在第几个char里,然后再num%8算出在哪一位
比如:23/8=2,在第二个char;23%8=7,在第七位上面。

那如何把任意一位置1,且不改变其他位?

95c5eb9618d6410d991433853d0ece97.png

把它和左移(向高位移动)以后的1(即其他位是0,只有要改变那一位是1)和原来的数进行或运算,就可以得到结果。保证了其他位不变,只有该位被改变为了1.

那到底怎么移动呢?

67e2fd00263f4badbd96af2ea3e6707c.png(一个char中)

 

那可能有人就会想,这会不会跟大小端有关系,数据在内存中的存储形式???

错错错,大错特错,首先大小端只存在于大于1字节的数据类型中,其次不管从哪边移动,本质是向高位或者低位移动。

所以说,%8以后,是哪一位,1直接左移几位(即向高位移动)。

 

那么在把某一位置为1以后,要重新置为0的话,应该怎么搞呢?

同理得:直接将1移位以后,再取反,将结果和原数进行与运算。

 

那要测试这个数在不在位图中,怎么测试呢?也就是看某一位是不是1

直接返回  1移位以后和原数相与的结果,不为0则存在,为0则不存在。

 

我们来看代码实现:


template<size_t N>
	class bitset
	{
	public:
		bitset()
		{
			//_bit.resize((N/8) + 1, 0);
			_bit.resize((N >> 3) + 1, 0);//左移3位就相当于/8,效率更快一些,但要注意运算符的优先级
		}

		void set(size_t x)
		{
			size_t i = x >> 3;
			size_t j = x % 8;
			_bit[i] |= (1 << j);//在知道是哪一个char之后,直接把这一个char相与。
		}

		void reset(size_t x)
		{
			size_t i = x >> 3;
			size_t j = x % 8;
			_bit[i] &= (~(1 << j));
		}
		bool test(size_t x)
		{
			size_t i = x >> 3;
			size_t j = x % 8;
			return _bit[i] & (1 << j);
		}
	private:
		vector<char> _bit;
	};

2.位图的应用

1. 给定100亿个整数,设计算法找到只出现一次的整数?
 
统计次数的话,那么就需要两个位图来实现,两个比特位来统计00(0次),01(1次),10(2次及以上)。
7c7358063a1b4cf0ba1f70f1c4c1a733.png
直接上代码:
template<size_t n>
	class two_bitset
	{
	public:
		void set(size_t x)
		{
			if (!_bs1.test(x) && !_bs2.test(x))//00
			{
				_bs2.set(x); //0次变1次
			}
			else if (!_bs1.test(x) && _bs2.test(x))//01
			{
				_bs1.set(x);
				_bs2.reset(x);//1次变两次
			}
		}
		void printonce()
		{
			for (size_t i = 0; i < n; ++i)
			{
				if (!_bs1.test(i) && _bs2.test(i))
				{
					cout << i << endl;
				}
			}
			cout << endl;
		}
	private:
		bitset<n> _bs1;
		bitset<n> _bs2;
	};

一个数如果在两个位图中的同一位置都是0,那么说明就是0次 ,再进来的数就要将00第二位set为01,表示出现一次,后面同理可得。

2. 给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件交集?
首先我们知道,整数的范围最大是42亿多,所以100亿个整数中,一定存在许多重复的整数。
所以将文件中的数据都放入位图中,只看 存在或者不存在两种状态,这就自动去掉了重复的数,某一位一直是1。只有512MB,可以存入内存中进行处理。
然后两个位图进行相与操作,同时为1说明是交集。
 
3. 位图应用变形:1个文件有100亿个int,1G内存,设计算法找到出现次数不超过2次的所有整
首先我们知道,整数的范围最大是42亿多,所以100亿个整数中,一定存在许多重复的整数。
所以将文件中的数据都放入位图中,只看 存在或者不存在两种状态,占用内存很少。
通过两个位图来表示次数,00(0次),01(1次),10(2次),11(3次及以上),然后控制条件(只找01,10)输出结果,和第一个问题其实是一样的。

二、哈希切分

还是一道面试题来引入哈希切分:

给一个超过100G大小的log fifile, log中存着IP地址, 设计算法找到出现次数最多的IP地址?
 
由前面所学,我们可能会想到位图,但是行不通,要统计次数就要开辟多个位图,
7c7358063a1b4cf0ba1f70f1c4c1a733.png
成倍的开辟位图来表示次数的话,会占用大量的内存空间,内存也存不下。
 
我们用的是map,但是在用map之前,要把大文件处理:
 
那我们就可以利用哈希的思想来把100G的文件分成100个小文件,每个1G,那么不就可以进内存了吗?
 
那怎么分???平均分?那当然不行,平均分对于分散的ip地址,都不在同一个小文件中,进入内存用map统计时,结果是不正确的。
 
直接哈希切分!!
ab9afb39fdfd4ef291128eddc6150928.png

 我们可以对100G大文件中的ip进行哈希切分,利用哈希表的思想,将哈希值相同的放入同一个小文件中,然后通过一个一个的小文件进入内存读取并统计个数,搞完一个clear掉,记录再进下一个。

理想很美好,现实却有点骨感?

单个文件超过1G:

因为存在哈希冲突,在数据进入小文件时,就会产生下面两种情况:

        1.一个小文件中,差不多都是不重复的数据,且个数还挺多,且map再加额外开销,导致内存很大,直接报错。

        2.一个小文件中,都是很多重复的数据,且个数还挺多,但是map却可以存下(重复的只增加次数),可以统计。

 

所以我们无需判断是哪种情况,直接无脑map,第一种情况发生就抛异常,捕获以后,换另一种哈希函数,再进行递归分割,拆成更小的文件后用map统计次数。

你懂了吗?99a3372c120248e988246197668c6d39.gif

与上题条件相同,如何找到top K的IP?如何直接用Linux系统命令实现?
利用堆来解决topK问题。

三、布隆过滤器

1.布隆过滤器的概念

开始讲布隆过滤器之前,我们要说一说位图的缺点是什么?

最大的缺点就是:1.开空间得看数据范围,一般要求范围集中,分散的话空间消耗就会上升

                             2.只能针对整型

如果给了一堆字符串,可不可以使用位图判断是否存在呢?

当然可以,可以使用哈希函数,将字符串转化为整型,再去映射到位图中。

当针对字符串来判断是否存在时,位图+哈希其实就是我们要讲的布隆过滤器。

布隆过滤器是由布隆(Burton Howard Bloom)在1970年提出的 一种紧凑型的、比较巧妙的
率型数据结构,特点是 高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存
在”,它是用多个哈希函数,将一个数据映射到位图结构中。此种方式 不仅可以提升查询效率,也
可以节省大量的内存空间
 
话不多说,上例子来理解这段话:
769846771ca0448f9e778f137b7d6d1b.png
 
当不同的字符串通过哈希函数转化为整型映射到位图中时,就会发生哈希碰撞!
比如find通过哈希函数可能和insert映射到同一位置,那么当find不存在时,但是他的位置的确已经被置为1,所以这就导致了:
                         判断存在是不准确的
                        判断不存在一定是准确的,因为位置是0,那一定不存在
7114a1caefd0494e8e2aa6f16db365b3.png

于是,我们就要想一些办法,让他的误判率低一些:

可以增加不同的哈希函数,转化为不同的哈希值,去映射到多个位置,降低误判率

bdd28914f4ee4493ab92a9c47ea36572.png

 这样的话,我们可以看到,只有当一个字符串映射的全部位置都置为1时,这个数才可能存在,说的是可能存在,因为也可能存在哈希碰撞。但降低了哈希碰撞的概率,降低了误判率。cb744e0dcd6447f1a58483d165f5ea5c.gif

那还有问题就是:一个字符串映射多个位图的位置,那位图应该开多大呢?

                             或者说如何选择哈希函数个数和布隆过滤器长度?

直接上大佬:大佬研究出来的一个公式:

9b929a81fde64d5e9ffcafe45e74f389.png

 现在来实现布隆过滤器:

f7006d4cdf994d91989a52bbb46874f0.png

template<
		size_t N,
		size_t M,
		class K = string,
		class Hash1= BKDRHash,
		class Hash2= APHash,
		class Hash3 = DJBHash>
	class Bloomfilter
	{
	public:
		void set(const K& key)
		{
			size_t i = Hash1()(key) % (N * M);
			size_t j = Hash2()(key) % (N * M);
			size_t k = Hash3()(key) % (N * M);

			_bs.set(i);
			_bs.set(j);
			_bs.set(k);
		}

		//void reset() 没有reset的原因是:因为存在哈希冲突,修改一个数的哈希值映射位置的值,会影响到其他的数,导致结果不准确。
		//硬要有reset,就需要计数,通过计数(--)来控制,那就需要成倍的位图来表示个数,严重浪费内存空间。

		//布隆过滤器,存在哈希冲突,所以确定不了一定存在的值
		//但是可以确定一定不存在的值
		bool test(const K& key)
		{
			size_t i = Hash1()(key) % (N * M);
			if (!_bs.test(i))
			{
				return false;
			}
			size_t j = Hash2()(key) % (N * M);
			if (!_bs.test(j))
			{
				return false;
			}
			size_t k = Hash3()(key) % (N * M);
			if (!_bs.test(k))
			{
				return false;
			}
			//到这里说明可能存在
			return true;
		}
	private:
		bitset <N* M> _bs;
	};

那布隆过滤器支持删除吗?当然不支持!

没有reset(不可以删除)的原因是:因为存在哈希冲突,修改一个数的哈希值映射位置的值,会影响到其他的数,导致结果不准确。

硬要有reset,就需要计数,通过计数(--)来控制,那就需要成倍的位图来表示个数,严重浪费内存空间。

2a3f5ccb62c44bcd9447367116781255.png

如上图所示这样实现 

2.布隆过滤器的应用

1.日常应用中,最常见的场景:

8ba05ab8855942ccb571aa8c9dc9559d.png

当数据量比较大时,会存放在磁盘中,磁盘访问速度相对来说很慢,所以在客户端和服务器中间加入布隆过滤器就会很大程度上加快访问速度,提高效率。

在过滤器阶段,数据不存在时,直接返回不存在;存在时,是可能存在(因为存在哈希冲突),所以会继续访问磁盘中的数据,数据在磁盘中存在即存在,不存在返回不存在。

2. 给两个文件,分别有100亿个query,我们只有1G内存,如何找到两个文件交集?分别给出
精确算法和近似算法。
 
query-般是查询指令,比如可能是一个网络请求, https://zhuanlan.zhihu.com/p/43263751/
或者是一个数据库sq|语句
假设平均每个query是50byte, 100亿个query合计多少内存? -- 500G
 
精确算法:交集中一定是准确的(哈希切分)
近似算法:那么一定是允许有误判的情况(有误差),那么就可以使用布隆过滤器。
 
当看到这个题目时,可能就会想到位图来解决,但是100亿个字符串都是不相同的,100亿个字符串已经超过了1G,不可行。
 
精确算法:
利用哈希函数,将100亿个query是500G,因为要到内存中比较两个文件,所以需要分为1000个小文件,每个小文件占用0.5G,那么两个小文件就可以都进内存中比较了。
如图所示:
5ce3cf30dd31485cbcbe258cde812f10.png

 当然也会出现哈希冲突超过0.5G的情况,若是重复数较多,但是我们是找交集,所以用位图来存或不在时,0.5G的小文件中数据个数占的内存一定小于0.5G,然后两个位图相与即可。

但如果是都不重复,就需要递归继续分割。用位图找交集

四、总结

不同的场景需要我们灵活的去找适合的方法去解决问题。

我们下期再见!3c06ef1b9607446d91d70f84d8b371c0.gif

 

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

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

相关文章

chapter-5 数据库设计

以下课程来源于MOOC学习—原课程请见&#xff1a;数据库原理与应用 考研复习 引言 设计的时候: 我们为什么不能设计成R&#xff08;学号&#xff0c;课程号&#xff0c;姓名&#xff0c;所咋系&#xff0c;系主任&#xff0c;成绩&#xff09;&#xff1f; 因为存在数据冗余…

BGP与OSPF混合组网

如图。R1和R2之间是OSPF Area 0,R23和R4之间是OSPF Area 1,R5和R6之间是OSPF Area2。除了R1和R2之间的cost是100,其余链路的cost都是10. AR1/2/3/4/5/6之间通过Loopback口建立IBGP全互联邻居关系,并且都是AS11520,和外部建立EBGP邻居访问100.100.100.1的网络。(不确定图中…

企业内训视频如何防范被盗录和下载?

企业内训视频如何防范被盗录和下载&#xff1f; 1. 【防下载】&#xff1a;实现视频文件的加密混淆、防下载&#xff1b; 2. 【防录屏】&#xff1a;A.&#xff08;ID跑马灯防录屏&#xff09;实现不同学员观看视频&#xff0c;实时显示该学员的姓名手机号时间&#xff1b;B.&…

线性代数 --- 最小二乘在直线拟合上的应用与Gram-Schmidt正交化(下)

在上一篇文章中&#xff0c;通过一个例子来说明最小二乘在拟合直线时所发挥的作用&#xff0c;也通过两个插图的比较进一步的阐明了投影与最小化e之间的密切关系。 线性代数 --- 最小二乘在直线拟合上的应用与Gram-Schmidt正交化&#xff08;上&#xff09;_松下J27的博客-CSD…

【UE Sequencer系列】08-副镜头切换、摄像机绑定摇臂的使用、摄像机绑定滑轨的使用

目录 一、副镜头切换 二、摄像机绑定摇臂的使用 三、摄像机绑定滑轨的使用 一、副镜头切换 1. 为“shot_05”新建镜头&#xff08;复制资产&#xff0c;创建新的关卡序列&#xff09; 同样的步骤再创建一个“Shot_07_02” 此时我们就可以对“Shot_07”中的两个副镜头进行切换…

最简单明了vite+ts+sass无loaders配置,利用ts读取scss文件中的变量并在App.vue中使用

做后端的&#xff0c;前端水平有限&#xff0c;最近练手&#xff0c;遇到了左侧菜单是展开关闭的问题&#xff0c;接触到了scss中定义全局变量&#xff0c;利用typescript读取的问题&#xff0c;在此记录一下 vitetssass 环境&#xff1a;package.json中内容如下&#xff0c;…

利用KMean算法进行分类

什么是KMean算法&#xff1f;简要说明什么是KMean算法&#xff0c;以及KMean算法的应用场景。 KMeans是一种聚类算法&#xff0c;它将数据集分成K个不同的类别&#xff08;簇&#xff09;&#xff0c;使得每个数据点都属于一个簇&#xff0c;并且每个簇的中心点&#xff08;质…

网络通信的安全性(HTTPS)

网络通信的安全性&#xff08;HTTPS&#xff09; 互联网是由无数网络节点组成的&#xff0c;两点之间的通信一般会经过很多个网络节点&#xff0c;因此&#xff0c;我们难免会有疑问&#xff1a; 作为发送方&#xff0c;我发送的消息内容会不会被中间人看到&#xff1f;&…

4.2 矩阵乘法的Strassen算法

1.伪代码以及用到的公式 ​ ​ ​ 2.代码 package collection; ​ public class StrassenMatrixMultiplication {public static int[][] multiply(int[][] a, int[][] b) {int n a.length;int[][] result new int[n][n]; ​if (n 1) {result[0][0] a[0][0] * b[0][0]…

为什么是毫末智行成为了DriveGPT的破壁人?

作者 | 魏启扬 来源 | 洞见新研社 毫末智行有着天生的紧迫感。 很多科技公司一年才举办一次的品牌日活动&#xff0c;毫末智行硬是办成了一个季度一次&#xff0c;活动频次的提高&#xff0c;则意味着组织内部新陈代谢的提速&#xff0c;从研发到落地乃至运营&#xff0c;都要…

ChatGPT 这个风口,普通人怎么抓住?

最近在测试ChatGPT不同领域的变现玩法&#xff0c;有一些已经初见成效&#xff0c;接下来会慢慢分享出来。 今天先给大家分享一个&#xff0c;看完就能直接上手的暴力引流玩法。 所需工具&#xff1a; 1&#xff09;ChatGPT&#xff08;最好是plus版&#xff0c;需要保证快速…

一个开源的大型语言模型LLaMA论文简单解读,LLaMA: Open and Efficient Foundation Language Models

一个开源的大型语言模型LLaMA论文解读&#xff0c;LLaMA: Open and Efficient Foundation Language Models返回论文和资料目录 1.导读 LLaMA 是 Meta AI 发布的包含 7B、13B、33B 和 65B 四种参数规模的基础语言模型集合&#xff0c;LLaMA-13B 仅以 1/10 规模的参数在多数的 …

ASEMI代理AD8638ARJZ-REEL7原装ADI车规级AD8638ARJZ-REEL7

编辑&#xff1a;ll ASEMI代理AD8638ARJZ-REEL7原装ADI车规级AD8638ARJZ-REEL7 型号&#xff1a;AD8638ARJZ-REEL7 品牌&#xff1a;ADI /亚德诺 封装&#xff1a;SOT-23-5 批号&#xff1a;2023 安装类型&#xff1a;表面贴装型 引脚数量&#xff1a;5 类型&#xff1…

初谈 ChatGPT

引子 最近&#xff0c;小编发现互联网中的大 V 突然都在用 ChatGPT 做宣传&#xff1a;“ChatGPT不会淘汰你&#xff0c;能驾驭ChatGPT的人会淘汰你”、“带领一小部分人先驾驭ChatGPT”。 确实&#xff0c;ChatGPT这个新生事物&#xff0c;如今被视为蒸汽机、电脑、iPhone 般的…

【ChatGPT 】国内无需注册 openai 即可访问 ChatGPT:ChatGPT Sidebar 浏览器扩展程序的安装与使用

一、前言 问题&#xff1a;国内注册 openai 账号麻烦&#xff0c;新必应有部分人也无法登录成功&#xff0c;存在域名单点登录失败等问题&#xff0c;所以无法真正使用 ChatGPT 解决&#xff1a;大部分人仅需使用 ChatGPT 的搜索功能&#xff0c;无需真正对话&#xff0c;需要…

云计算技术的现状和未来发展趋势分析

近年来&#xff0c;随着互联网、物联网、大数据等技术的不断发展&#xff0c;云计算技术也随之崛起并迅速发展。云计算技术作为一种新的计算模式&#xff0c;不断地改变着我们的生活和工作方式&#xff0c;成为了当今IT产业的一股强大的力量。本文将详细探讨云计算技术的现状和…

第十天面试实战篇

目录 一、springboot的常用注解&#xff1f; 二、springmvc常用注解&#xff1f; 三、mysql的内连接和外连接有什么区别&#xff1f;比如有两张表&#xff1a;A和B内连接只返回两个表A和B的交集部分 四、redis分布式锁的缺点有哪些&#xff1f; 五、如何使用reddssion解决r…

多个渠道成功销售的秘诀速递

将您的电子商务业务扩展到多个渠道销售似乎是一项艰巨的任务吗&#xff1f;但如果有了正确的多渠道增长战略&#xff0c;这可能是实现快速增长的好方法。当然&#xff0c;您需要考虑借助一些工具与策略&#xff0c;而SaleSmartly&#xff08;ss客服&#xff09;可以为您提供。 …

Python做个猫狗识别系统,给人美心善的邻居

嗨害大家好鸭&#xff01;我是爱摸鱼的芝士❤ 宠物真的看着好治愈 谁不想有一只属于自己的乖乖宠物捏~ 这篇文章中我放弃了以往的model.fit()训练方法&#xff0c; 改用model.train_on_batch方法。 两种方法的比较&#xff1a; model.fit()&#xff1a;用起来十分简单&#…

【剧前爆米花--爪哇岛寻宝】java文件操作和io流

作者&#xff1a;困了电视剧 专栏&#xff1a;《JavaEE初阶》 文章分布&#xff1a;这是一篇关于文件操作的文件&#xff0c;介绍了文件读写以及相关对象的内容&#xff0c;希望对你有所帮助&#xff01; 目录 文件操作 文件路径 绝对路径 相对路径 File类 File类的构造方…