Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >【C++】哈希(位图,布隆过滤器)

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

作者头像
The sky
发布于 2023-04-12 06:55:00
发布于 2023-04-12 06:55:00
31100
代码可运行
举报
文章被收录于专栏:C++的逆袭之路C++的逆袭之路
运行总次数:0
代码可运行

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



一、位图

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,内存很小。

举例说明:

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

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

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

那到底怎么移动呢?

(一个char中)

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

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

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

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

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

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

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

我们来看代码实现:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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次及以上)。

直接上代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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地址?

由前面所学,我们可能会想到位图,但是行不通,要统计次数就要开辟多个位图,

成倍的开辟位图来表示次数的话,会占用大量的内存空间,内存也存不下。

我们用的是map,但是在用map之前,要把大文件处理:

那我们就可以利用哈希的思想来把100G的文件分成100个小文件,每个1G,那么不就可以进内存了吗?

那怎么分???平均分?那当然不行,平均分对于分散的ip地址,都不在同一个小文件中,进入内存用map统计时,结果是不正确的。

直接哈希切分!!

 我们可以对100G大文件中的ip进行哈希切分,利用哈希表的思想,将哈希值相同的放入同一个小文件中,然后通过一个一个的小文件进入内存读取并统计个数,搞完一个clear掉,记录再进下一个。 理想很美好,现实却有点骨感? 单个文件超过1G: 因为存在哈希冲突,在数据进入小文件时,就会产生下面两种情况:         1.一个小文件中,差不多都是不重复的数据,且个数还挺多,且map再加额外开销,导致内存很大,直接报错。         2.一个小文件中,都是很多重复的数据,且个数还挺多,但是map却可以存下(重复的只增加次数),可以统计。 所以我们无需判断是哪种情况,直接无脑map,第一种情况发生就抛异常,捕获以后,换另一种哈希函数,再进行递归分割,拆成更小的文件后用map统计次数。 你懂了吗?

与上题条件相同,如何找到top K的IP?如何直接用Linux系统命令实现?

利用堆来解决topK问题。


三、布隆过滤器

1.布隆过滤器的概念

开始讲布隆过滤器之前,我们要说一说位图的缺点是什么? 最大的缺点就是:1.开空间得看数据范围,一般要求范围集中,分散的话空间消耗就会上升                              2.只能针对整型 如果给了一堆字符串,可不可以使用位图判断是否存在呢? 当然可以,可以使用哈希函数,将字符串转化为整型,再去映射到位图中。

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

布隆过滤器是由布隆(Burton Howard Bloom)在1970年提出的 一种紧凑型的、比较巧妙的

率型数据结构,特点是 高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存

在”,它是用多个哈希函数,将一个数据映射到位图结构中。此种方式 不仅可以提升查询效率,也

可以节省大量的内存空间

话不多说,上例子来理解这段话:

当不同的字符串通过哈希函数转化为整型映射到位图中时,就会发生哈希碰撞! 比如find通过哈希函数可能和insert映射到同一位置,那么当find不存在时,但是他的位置的确已经被置为1,所以这就导致了: 判断存在是不准确的                         判断不存在一定是准确的,因为位置是0,那一定不存在

于是,我们就要想一些办法,让他的误判率低一些: 可以增加不同的哈希函数,转化为不同的哈希值,去映射到多个位置,降低误判率

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

那还有问题就是:一个字符串映射多个位图的位置,那位图应该开多大呢? 或者说如何选择哈希函数个数和布隆过滤器长度? 直接上大佬:大佬研究出来的一个公式:

 现在来实现布隆过滤器:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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,就需要计数,通过计数(--)来控制,那就需要成倍的位图来表示个数,严重浪费内存空间。

如上图所示这样实现 

2.布隆过滤器的应用

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

当数据量比较大时,会存放在磁盘中,磁盘访问速度相对来说很慢,所以在客户端和服务器中间加入布隆过滤器就会很大程度上加快访问速度,提高效率。 在过滤器阶段,数据不存在时,直接返回不存在;存在时,是可能存在(因为存在哈希冲突),所以会继续访问磁盘中的数据,数据在磁盘中存在即存在,不存在返回不存在。

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,那么两个小文件就可以都进内存中比较了。 如图所示:

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

四、总结

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

我们下期再见!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-04-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
【C++高阶】哈希—— 位图 | 布隆过滤器 | 哈希切分
之前我们已经在这篇文章中 【C++高阶】哈希函数底层原理全面探索和深度解析-CSDN博客
IsLand1314
2024/10/15
1450
【C++高阶】哈希—— 位图 | 布隆过滤器 | 哈希切分
C++:位图和布隆过滤器
问题:给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在 这40亿个数中。【腾讯】
小陈在拼命
2024/05/10
1040
C++:位图和布隆过滤器
【C++】位图应用 | 布隆过滤器
给40亿个不重复的无符号整数,没排过序,给一个无符号整数,如何快速判断一个数是否在这40亿个数中
lovevivi
2023/10/17
1960
【C++】位图应用 | 布隆过滤器
【C++从小白到大牛】布隆过滤器
上一篇文章我们已经学习了位图的应用,但是位图一般只能处理整形,如果内容编号是字符串,就无法处理了。而我们又知道如果只用哈希表存储用户记录,缺点就是浪费空间。但是我们将哈希表和位图结合起来呢,就是我们的布隆过滤器!
用户11316056
2024/10/16
920
【C++从小白到大牛】布隆过滤器
【C++】BloomFilter——布隆过滤器
位图的优点是节省空间,快,缺点是要求范围相对集中,如果范围分散,空间消耗上升,同时只能针对整型,字符串通过哈希转化成整型,再去映射,对于整型没有冲突,因为整型是有限的,映射唯一的位置,但是对于字符串来说,是无限的,会发生冲突,会发生误判:此时的情况的是不在是正确的,在是不正确的,因为可能不来是不在的,但是位置跟别人发生冲突,发生误判
平凡的人1
2023/10/15
4350
【C++】BloomFilter——布隆过滤器
【C++航海王:追寻罗杰的编程之路】哈希的应用——位图 | 布隆过滤器
位图的概念:所谓位图,就是用每一位来存放某种状态,适用于海量数据,数据无重复的场景。通常是用来判断某个数据是否存在的。
枫叶丹
2024/07/15
1180
【C++航海王:追寻罗杰的编程之路】哈希的应用——位图 | 布隆过滤器
哈希的应用——位图
所以上面这些思路都不太合适,而且我们这里只是要判断在不在,其实没必要把它们全部存起来。
YIN_尹
2024/01/23
1560
哈希的应用——位图
哈希应用全解
所谓位图(bitset),就是用每一位来存放某种状态,适用于海量数据,数据无重复的场景。通常是用来判断某个数据存不存在的。
小灵蛇
2024/06/06
1430
哈希应用全解
【数据结构】哈希经典应用:布隆过滤器(哈希+位图)——[深度解析](9)
YY的秘密代码小屋
2024/01/23
2160
【数据结构】哈希经典应用:布隆过滤器(哈希+位图)——[深度解析](9)
【C++修炼之路】24.哈希应用--位图
数据是否在给定的整形数据中,结果是在或者不在,刚好是两种状态,那么可以使用一个二进制比特位来代表数据是否存在的信息,如果二进制比特位为1,代表存在,为0代表不存在。
每天都要进步呀
2023/03/28
2580
【C++修炼之路】24.哈希应用--位图
【C++】哈希应用:位图 哈希切分 布隆过滤器
1. 大厂经典的面试题,给你40亿个不重复的无符号整数,让你快速判断一个数是否在这40亿个数中,最直接的思路就是遍历这40亿个整数,逐一进行比对,当然这种方式可以倒是可以,但是效率未免太低了。 另一种方式就是排序+二分的查找,因为二分查找的效率还是比较高的,logN的时间复杂度,但是磁盘上面无法进行排序,排序要支持下标的随机访问,这40亿个整数又无法加载到内存里面,你怎么进行排序呢?所以这样的方式也是不可行的。 那能不能用红黑树或者哈希表呢?红黑树查找的效率是logN,哈希表可以直接映射,查找的效率接近常数次,虽然他们查找的效率确实很快,但是40亿个整数,那就是160亿字节,10亿字节是1GB,16GB字节红黑树和哈希表怎么能存的下呢?这还没有算红黑树的三叉链结构,每个结点有三个指针,而且哈希表每个结点会有一个next指针,算上这些的话需要的内存会更大,所以用红黑树或哈希表也是无法解决问题的。
举杯邀明月
2023/04/12
6080
【C++】哈希应用:位图 哈希切分 布隆过滤器
【C++进阶】位图/布隆过滤器与海量数据处理
我们知道数据的最小存储位是比特(bite),每个比特位只能是0或1,我们用1和0来表示在或不在,或是其它类型的状态信息,这种结构称作位图。
aosei
2024/01/23
1450
【C++进阶】位图/布隆过滤器与海量数据处理
C++哈希应用-位图/布隆过滤器/海量数据处理
C++位图/布隆过滤器/海量数据处理 零、前言 一、位图 1、位图概念 2、位图接口的介绍以及实现 3、位图的应用 二、布隆过滤器 1、布隆过滤器概念和介绍 2、布隆过滤器的操作及实现 3、布隆过滤器的分析 三、海量数据处理 零、前言 本章主要讲解C++中对哈希的应用有关方面的内容,位图,布隆,海量数据处理 一、位图 1、位图概念 位图概念: 位图其实就是哈希的变形,同样通过映射来处理数据,只不过位图本身并不存储数据,而是存储标记 通过一个比特位来标记这个数据是否存在,1代表存在,0代表不
用户9645905
2022/11/30
5200
C++哈希应用-位图/布隆过滤器/海量数据处理
极致高效的数据处理:位图、布隆过滤器与哈希切分的奇妙之旅
本文将带领你深入探索哈希的三大高效应用——位图、布隆过滤器和哈希切分。它们如同精巧的齿轮,共同驱动着现代计算系统的高效运作。从减少存储空间到加速查找效率,从数据去重到流式处理,这些技术在幕后悄然发挥着巨大的力量。让我们一起揭开哈希算法的神秘面纱,领略数据处理的艺术之美。
suye
2024/11/19
1390
极致高效的数据处理:位图、布隆过滤器与哈希切分的奇妙之旅
【C++高阶】哈希之美:探索位图与布隆过滤器的应用之旅
前言:在数据科学的浩瀚星空中,哈希函数犹如一颗璀璨的星辰,以其独特的光芒照亮了数据处理的每一个角落。哈希,这一简单而强大的技术,通过将任意长度的输入(如字符串、数字等)映射到固定长度的输出(即哈希值),实现了数据的快速定位与索引。然而,哈希的魅力远不止于此,当它与位图和布隆过滤器相结合时,更是催生出了一系列高效且实用的数据处理方案
Eternity._
2024/08/05
990
【C++高阶】哈希之美:探索位图与布隆过滤器的应用之旅
哈希应用
所谓位图,就是用每一个比特位来存放某种状态,适用于海量数据,数据无重复的场景。通常是用来判断某个数据存不存在的
芝士就是菜
2023/04/20
4260
哈希应用
位图/布隆过滤器/海量数据处理方式
所谓位图,就是用每一位来存放某种状态,适用于海量数据,数据无重复的场景。通常是用来判断某个数据存不存在的。
二肥是只大懒蓝猫
2023/03/30
3770
位图/布隆过滤器/海量数据处理方式
位图与布隆过滤器
此篇文章针对大量数据进行筛选或者判断重复,存在等一系列侧重于在特定空间,时间等允许范围内进行选择的问题提出解决方案。
用户11458826
2025/01/23
830
位图与布隆过滤器
哈希表
哈希表是一种数据结构。它通过哈希函数把数据和位置进行映射,来实现快速的寻找、插入和删除操作。
code-child
2023/05/30
2830
哈希表
C++哈希应用——布隆过滤器
用哈希表存储用户记录,缺点是需要消耗较大的内存;用位图存储用户记录,缺点是位图一般处理整形,内容是字符串或者自定义类型就很勉强。基于以上,若将哈希和位图结合,称为布隆过滤器,会不会把上面的问题都解决了呢?
梨_萍
2023/04/25
4750
C++哈希应用——布隆过滤器
相关推荐
【C++高阶】哈希—— 位图 | 布隆过滤器 | 哈希切分
更多 >
领券
社区富文本编辑器全新改版!诚邀体验~
全新交互,全新视觉,新增快捷键、悬浮工具栏、高亮块等功能并同时优化现有功能,全面提升创作效率和体验
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验