C++学习进阶:哈希思想的进一步体现

目录

 

前言

1.位图

1.1.位图的实现与原理

1.2.如何使用位图处理海量数据

2.布隆过滤器

2.1.知识引入

2.2.布隆过滤器的实现

2.3.布隆过滤器的应用

3.哈希切割


 

前言

我们在之前对哈希表的学习,明白了哈希的本质就是一种映射!!!只要是实现了映射关系我们就能够说这个是“哈希”。 

哈希:通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素。

在我们实际开发中,大部分场景是处理海量的数据,例如在博客中3.3中提及的如何在40亿数据中判断某个数据在不在的问题。因为哈希映射关系的这个思路,我们最终就可以通过更小的比特位来实现数据之间的映射。C++学习进阶:二进制与位运算-CSDN博客 

1.位图

1.1.位图的实现与原理

位图(Bitmap)通常用于表示一个二进制数据的集合,其中每个位可以独立地设置或清除。位图特别适用于需要快速检查某个元素是否存在于集合中的情况,因为每个元素的状态可以通过单个位来表示,这通常比使用其他数据结构(如数组或链表)更节省空间。

位图的哈希原理:通过二进制位来表示其他数据,实现判断任意一个数num是否存在的问题,等价于判断num映射的比特位是0还是1。

位图实现:

  1. 我们构建元素均为0的int类型数组,这样子我们就能得到 与数组大小一致的size个 的32位全为0的比特位流6c842f8fa7214c1d8586ba0813dbf3b5.png
  2. 因为数据大多时候是无序的,但是我们在比特流中是有序的,比如data =60,我们映射在第2段32位比特流的第28个比特位上设置为1。
  3. 那我们对于传入的一个数据data,我们如何找到他对应比特位的位置呢?因为这个比特流由size个32位的00000000 0000000 0000000构成,那么我们查找是第几个就通过data/32可以得到,通过data%32就可知道在第几个比特位上
  4. 当我们找到数据num在第i个int和对应32位比特的第j个下标时,我们现在的需求就是只将当前这个int的第j个比特位设为1或者0,并且不影响其他的比特位
  5. 当我们可以完成4的需求时,下一步就是判断num在不在这个比特流中了,也就是查找第i个int和对应32位比特的第j个下标是0还是1.

代码图解和实现: 

set的思路:

fa0dcd30914343c7b653149edd0dc549.png

rset的思路 :

1bbc10fa19e945139df9886f274633ef.png

test的思路:

cda8e7248a914eefa7bafc012ad2875a.png

	// N为非类型模版参数
	template<size_t N>
	class bitset
	{
	public:
		// N为比特位个数
		bitset()
		{
			// 开辟空间大小,所有整型均设为0
			_bits.resize(N / 32 + 1, 0);
		}

		// 添加这个值,把对应比特位的数据置为1
		void set(size_t x)
		{
			// 找到数组中的第几个整型
			size_t i = x / 32;
			// 找到这个整型下的第几个比特位
			size_t j = x % 32;
			// 将1左移j位置,到达映射位,通过 或 可以把这一位变为1其他位不变
			// 因为任何数 或1 都是 1, 1左移j位其他位置全为0,不会对其他位影响
			_bits[i] |= (1 << j);
		}
		// 删除这个值,把对应比特位的数据置为0
		void reset(size_t x)
		{
			size_t i = x / 32;
			size_t j = x % 32;
			// 这里我们通过取反,使得第j位为0,其他位为1
			// 接着与一下,把对应映射的第j位变成0,其他位置因为是1,与 后均为本身
			_bits[i] &= ~(1 << j);
		}
		// 查找这个值在不在
		bool test(size_t x)
		{
			size_t i = x / 32;
			size_t j = x % 32;
			// 将1左移到对应位置与一下就好了

			if (_bits[i] & (1 << j))
				return true;
			else 
				return false;
		}

	private:
		vector<int> _bits;
	};

到了这里位图的具体操作我们就实现了,而库中提供的原生bitset有更多功能,大家有时间可以去读一下文档    bitset - C++ Reference

1.2.如何使用位图处理海量数据

 

例题一:有40亿个无序且不重复的无符号的整数,如何快速判断这个数是否在那40亿个数当中

我们通过位图就能实现O(1)的时间复杂度查找,这一点我们在位图的实现模块可以看出来,并且40亿个int类型的数据需要开辟16G的内存,而通过2^32次方个比特位映射出40亿个数据,所需空间仅为0.5G


void find_mass_data()
{
	// 文件操作将40亿个数据写入array中
	// 这里仅做模拟
	int array[] = {111,33,21,45};
 
	// 开辟42亿个比特位的位图
	bitset<0xffffffff> bs;
 
	for (auto e : array)
	{
		// 通过set实现映射
		bs.set(e);
	}
 
	int x = 9999999999;
	// 实现O(1)复杂度查找
	bool ifExit = bs.test(x);
 
}

 

例题二:给定100亿个整数,设计算法找到只出现一次的整数

查找出现1次的整数,那么我们就需要分多种情况,出现0次,出现1次和出现2次及以上。我们在上面的位图学习时,我们通过比特位0,1两种状态来表示不在和在。那么这三种情况如何表示呢?

我们可以通过两个位图来实现3种状态的表示,具体实现如下。

template<size_t N>
class twoBitset
{
public:
	void set(size_t x)
	{
		// 表示00->01
		if (_bs1.test(x) == false && _bs2.test(x) == false)
		{
			_bs1.reset(x);
			_bs2.set(x);
		}
		else if (_bs1.test(x) == false && _bs2.test(x) == true)
		{
			_bs1.set(x);
			_bs2.reset(x);
		}
		else if (_bs1.test(x) == true && _bs2.test(x) == false)
		{
			_bs1.set(x);
			_bs2.set(x);
		}
	}

	void findOnce()
	{
		for (size_t i = 0; i < N / 32; i++)
		{
			// 打印出现一次
			if (_bs1.test(i) == false && _bs2.test(i) == true)
				cout << i << endl;
		}
	}
private:
	bitset<N> _bs1;
	bitset<N> _bs2;
};

// 测试函数
void find_once_in_mass_data()
{
	zhong::twoBitset<1000> tbs;
	int array[] = { 1,3,3,4,6,9,1,2 };
	for (auto e : array)
	{
		tbs.set(e);
	}
	tbs.findOnce();
}

当然因为具体的场景还是得根据具体的实现,所以我们可以通过文件导入100亿个整数,然后分批进入array数组中,然后在开辟一个数组来存放出现一次的数据,循环往复就能够完成了。


 

例题三:给两个文件,分别有100亿个整数,如何找到两个文件的交集

这个我们就先把这个文件的数据%500,各自分成500个小文件,然后各自的小文件就写入set中,去重,然后再比较重合的数据。然后再开辟一个新的set来存放交集的数据,接着释放掉写入当前小文件数据的set,进行读取下一个文件,循环不断地把交集数据放在专门存储交集的set中,最后就完成了交集的查找了


 

例题四:1个文件中有100亿个int,1G内存设计算法找到出现次数不为2次的所有整数

这个就是先分割成小文件,然后把数据写入位图中,接着就是和例题二一样的操作了

2.布隆过滤器

2.1.知识引入

布隆过滤器(Bloom Filter)是由布隆在1970年提出的一种概率型数据结构。它实际上是一个很长的二进制向量和一系列随机映射函数。这种数据结构的主要特点在于其高效的插入和查询性能,常被用于检索一个元素是否存在于一个集合中。

布隆过滤器的特点:高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”。 

如何体会这一句话呢?接下来我们从布隆过滤器的底层开始讲起:

4bf41ccc3e014163b8c113f51771cc52.png

而布隆过滤器要解决的就是:保证哈希映射的数据结构高效的插入和查询功能的同时,让哈希冲突尽量小。(因为哈希冲突不可避免,体现在:字符串无限,而int有限)

解决方法:当我们进行插入或者查询时,定义多几个哈希函数,让同一个字符串映射多个int(多个int再映射到多个比特位),接着在判断函数处判断某一个字符串的映射是否同时满足这几个映射,那么就能够以极小的出错概率来判断存在了。

c03f04113ade429da2f1128bda6db0fa.png

接着我们问一个问题:对于存在哈希冲突的场景中,如何判断一个字符串是否存在的准确性?

  1. 字符串存在是不准确的,因为会出现哈希冲突,比如:abcd,和acbd如果单纯通过ASCII相加的哈希函数进行映射(值为394),最终两个的映射值是一致的,因为当我们查找到映射值为394,我们判断abcd存在,但是可能存在的是acbd
  2. 字符串的不存在是准确的,因为如果查找不到394,那么无论是abcd,还是acbd肯定都不在,当我们查找abcd时就能判断准确了

注意这里的例子我们通过简单情况来进行这个问题的解释,不过就算我们在实际场景下,还是会出现哈希冲突,也就是对应着复杂情况下的这个例子,因此还是很有意义的。

 到了这里布隆过滤器的知识我们大概就讲解完成了,更加深入的内容我们可以在这个链接中进行学习详解布隆过滤器的原理、使用场景和注意事项

2.2.布隆过滤器的实现

布隆过滤器 = 某种类型通过多个哈希函数映射成的int对应的位图,所以实现时我们需要多种哈希函数,这里我们以string类型为主要情况,哈希函数选择string类型。

struct APHash
{
	size_t operator()(const string& key)
	{
		size_t hash = 0;
		for (size_t i = 0; i < key.size(); i++)
		{
			char ch = key[i];
			if ((i & 1) == 0)
			{
				hash ^= ((hash << 7) ^ ch ^ (hash >> 3));
			}
			else
			{
				hash ^= (~((hash << 11) ^ ch ^ (hash >> 5)));
			}
		}
		return hash;
	}
};

struct DJBHash
{
	size_t operator()(const string& key)
	{
		size_t hash = 5381;
		for (auto ch : key)
		{
			hash += (hash << 5) + ch;
		}
		return hash;
	}
};

template<size_t N, class K = string,
	class HashFunc1 = BKDRHash,
	class HashFunc2 = APHash,
	class HashFunc3 = DJBHash>
class BloomFilter
{
public:
	void Set(const K& key)
	{
		size_t hash1 = HashFunc1()(key) % N;
		size_t hash2 = HashFunc2()(key) % N;
		size_t hash3 = HashFunc3()(key) % N;

		// 对于这个key我们实现3个映射
		_bs.set(hash1);
		_bs.set(hash2);
		_bs.set(hash3);
	}

	// 一般不支持删除,删除一个值可能会影响其他值
	// 非要支持删除,也是可以的,用多个位标记一个值,存引用计数
	// 但是这样话,空间消耗的就变大了
	// void Reset(const K& key);

	bool Test(const K& key)
	{
		// 对于当前key我们判断它的三个hash映射值
		size_t hash1 = HashFunc1()(key) % N;
		size_t hash2 = HashFunc2()(key) % N;
		size_t hash3 = HashFunc3()(key) % N;
		// 只要有一个哈希映射值在比特流中对应不为1
		if (_bs.test(hash1) == false || _bs.test(hash2) == false || _bs.test(hash3) == false)
			return false;
		else
			return true;
	}

private:
	bitset<N> _bs;
};

值得注意的是:我们通过3个哈希函数实现了3分映射,假如apple映射为99,182,912,那么当我们通过Test函数查找是,如果为这三个映射只要有一个不符合,那么就表示apple没有映射在这个位图中。如果恰好符合,可以说apple有一定存在么?

所以这里:我们能够判断某一个元素一定不在,但是无法判断一定在。 

2.3.布隆过滤器的应用

场景一:英雄联盟创建新用户的昵称

一般来说,用户数据是存放在服务器下的数据库中的,当用户创建昵称时,系统会避免昵称重复,所以当我们查询昵称是否重复时,我们可以通过访问数据库,进行字符串的数组的遍历比对来进行,理论上是可行的。但是这种做法需要频繁的进行客户端与服务器之间的访问数据库的请求,会消耗大量的资源……

实际场景中: 

e64a9719923c44e7ab3d62346c75e013.png

在这个场景中,布隆过滤器过滤了一定不存在数据库中的昵称表示可以直接使用,而通过过滤器映射为存在的昵称不一定被使用了,可能是存在哈希冲突,所以需要传入数据库进行字符串的比对。通过这个布隆过滤器,我们就减少了字符串比对的量,减少了直接访问全部字符串的开销。


3.哈希切割

哈希切割是一种常用的数据分割方法,它通过哈希函数将数据划分到不同的分区或桶中。哈希函数将数据映射到一个固定大小的哈希值,然后根据哈希值将数据分配到对应的分区中。

哈希切割的主要目的是将数据均匀地分布到多个分区中,以便实现数据的快速查找和访问。通过将数据分散到多个分区中,可以减少每个分区中的数据量,提高查询效率。

接下来我们通过两个场景来进行哈希切割的学习!!! 


场景一:两个文件,分别有100亿个query,我们只有1G内存,如何找到两个文件的交集。

100亿个数据库读取的query请求,我们可以假设它为50大小字节的字符串,那么就与5000亿个字节的字符串,我们知道1G大小约为10亿字节,所以完整地将这些数据写入内存需要500G内存显然是不符合当前场景的。

那么我们无法一次性地写入进内存,我们能不能够通过分批,切分写入内存呢?也就是能不能通过一种算法将大文件分割成小文件写入内存中在进行找交集呢? 

  1. 首先我们通过哈希函数对分别对两个文件的query进行映射,可以映射到大量的int值,然后我们对映射的值进行取模,就能够分割这些数据,即HashFunc(query)%500,实现了将大量的query对应的int分割成0~500份的小query
  2. 我们知道两个大文件中存在交集,那么就会出现两个文件交集部分通过哈希函数的映射值一定相同,那么分割后一定落在相同的小份的query中,这样就实现了哈希分割
  3. 对于分割后的小文件,对应的下标为0~500,通过相同的哈希函数分割后,交集一定会在相同文件的下标中,接下来我们定义两个set,然后把两个文件写入各自的set中。写入完成后,我们就能够通过遍历判断key是否一致来实现找到交集。

fa999db5925f48beb8f220c872173745.png

然而实际情况下,可能存在大部分的数据并不是重复的,也就是通过这样子分割的0~500个小query可能会存在,一些query占的内存10G,一些内存只占0.1G这样子,我们无法保证通过这个单一的哈希算法,实现均等分割成小文件。 

那么如何解决这个问题呢?

  1. 我们还是得继续分析一下,假设恰好有一个分割后文件对应的query大小为10G,这个文件中可能大部分为重复的数据,也可能为大部分都不是重复的数据。对于前者,我们可以通过set这个容器(set不允许重复数据写入),写入进内存中,这样子就可能实现少部分的非重复数据写入进内存中。
  2. 但是现实可能是骨感的,这时候对应着大部分都不是重复的数据,那么我们就通过其他的哈希函数,将这个很大的小文件进行二次的哈希分割,不断继续迭代。
  3. 可是我们如何判断对应的大部分不是重复数据呢?这种情况下我们通过set写入内存时,因为数据不是重复的,就会导致内存不够用,进而编译器会抛异常,那么我们就可以通过异常处理机制来作为这种情况的判断。
  4. 通过1,2,3我们就能够实现这个算法的迭代了!!!

822941a6e4e343199d33b03ec5c59478.png


场景二: 有一个100G的日志文件存放着IP地址,我们如何找到次数最多的IP地址

跟上一个的情况一致,我们都无法直接地将这个文件写入内存中。同样的我们也可以通过哈希分割成小文件,通过哈希映射再分割后相同的ip一定进入了相同的小文件中。然后不断地把小文件写入map中开始统计次数,用一个变量记录最大值。

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

安达发|APS智能优化排产软件之模具约束

在制造业中&#xff0c;模具是生产过程中不可或缺的重要工具。然而&#xff0c;由于模具的制造周期长、成本高以及生产过程中的复杂性&#xff0c;如何合理安排模具的使用和生产计划成为了一个关键问题。为了解决这个问题&#xff0c;许多企业开始采用APS&#xff08;高级计划与…

主干网络篇 | YOLOv8更换主干网络之VanillaNet | 华为方舟实验室提出全新轻量级骨干架构

前言:Hello大家好,我是小哥谈。华为方舟实验室所提出的VanillaNet架构克服了固有复杂性的挑战,使其成为资源受限环境的理想选择。其易于理解和高度简化的架构为高效部署开辟了新的可能性。广泛的实验表明,VanillaNet提供的性能与著名的深度神经网络和vision transformers相…

深度剖析Java中的String类

目录 引言 String类的特性 String类的部分实现代码&#xff1a; 不可变性&#xff1a; 补充&#xff1a; 常量池&#xff1a; 不可变性的好处 创建String对象 创建String对象的常用的三种方法如下&#xff1a; 使用常量串构造&#xff08;最常用&#xff09;&#xf…

帝国cms仿《鳄鱼下载站》网站源码

仿《鳄鱼下载站》网站源码手机安卓软件网站模版 PHP网站源码 帝国cms内核 采用帝国cms7.5 环境PHPmysql 恢复数据库后如何修改密码: 双击表&#xff0c;进入对应的详细数据表&#xff0c;然后找到&#xff1a;www_96kaifa_com_enewsuser这个表&#xff0c;双击打开修改&…

SAP SD学习笔记06 - 受注的据否,受注的理由,简易变更(一括处理)

上文讲了一括处理和Block&#xff08;冻结&#xff09;处理。 SAP SD学习笔记05 - SD中的一括处理&#xff08;集中处理&#xff09;&#xff0c;出荷和请求的冻结&#xff08;替代实现承认功能&#xff09;-CSDN博客 本章继续讲SAP的流程中一些常用的操作。 1&#xff0c;受注…

【算法】分治-快排

个人主页 &#xff1a; zxctscl 如有转载请先通知 题目 前言1. 75. 颜色分类1.1 分析1.2 代码 2. 912. 排序数组2.1 分析2.2 代码 3. 215. 数组中的第K个最大元素3.1 分析3.2 代码 4. LCR 159. 库存管理 III4.1 分析4.2 代码 前言 分治就是分而治之 1. 75. 颜色分类 1.1 分析…

基于java+springboot+vue实现的网上购物系统(文末源码+Lw+ppt)23-42

摘 要 随着我国经济的高速发展与人们生活水平的日益提高&#xff0c;人们对生活质量的追求也多种多样。尤其在人们生活节奏不断加快的当下&#xff0c;人们更趋向于足不出户解决生活上的问题&#xff0c;网上购物系统展现了其蓬勃生命力和广阔的前景。与此同时&#xff0c;为…

Linux【实战篇】—— NFS服务搭建与配置

目录 一、介绍 1.1什么是NFS&#xff1f; 1.2客户端与服务端之间的NFS如何进行数据传输&#xff1f; 1.3RPC和NFS的启动顺序 1.4NFS服务 系统守护进程 二、安装NFS服务端 2.1安装NFS服务 2.2 创建共享目录 2.3创建共享目录首页文件 2.4关闭防火墙 2.5启动NFS服务 2.…

Java 语言程序设计(基础篇)原书第10版 梁勇著 PDF 文字版电子书

简介 Java 语言程序设计&#xff08;基础篇&#xff09;原书第 10 版 是 Java 语言的经典教材&#xff0c;中文版分为基础篇和进阶篇&#xff0c;主要介绍程序设计基础、面向对象程序设计、GUI 程序设计、数据结构和算法、高级 Java 程序设计等内容。本书通过示例讲解问题求解…

抖音滑块验证码加密的盐的位置

最近更新后之前很容易找到盐的位置的方法变了&#xff0c;抖音特意把盐隐藏起来了 {"reply": "RJC","models": "yAd8rl","in_modal": "DTn0nD2","in_slide": "ou7H0Ngda","move": …

C++算法题 - 双指针

目录 125. 验证回文串392. 判断子序列167. 两数之和 Ⅱ - 输入有序数组11. 盛最多的水15. 三数之和 125. 验证回文串 LeetCode_link 如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后&#xff0c;短语正着读和反着读都一样。则可以认为该短语是一个 回文串 …

arm工作模式、arm9通用寄存器、异常向量表中irq的异常向量、cpsr中的哪几位是用来设置工作模式以及r13,r14,15别名是什么?有什么作用?

ARM 首先先介绍一下ARM公司。 ARM成立于1990年11月&#xff0c;前身为Acorn计算机公司 主要设计ARM系列RISC处理器内核 授权ARM内核给生产和销售半导体的合作伙伴ARM公司不生产芯片 提供基于ARM架构的开发设计技术软件工具评估版调试工具应用软件总线架构外围设备单元等等CPU中…

一起学习python——基础篇(20)

前言&#xff0c;之前经常从网上找一些免费的接口来测试&#xff0c;有点受制于人的感觉。想了想还不如直接写一个接口&#xff0c;这样方便自己测试。自己想返回什么格式就返回什么样子&#xff0c;不用担心服务报错&#xff0c;因为自己就可以完全掌控。然后宿舍二哥告诉我py…

spring boot集成logback到mysql 8

spring boot集成logback到mysql 8 依赖数据库准备创建log日志用户&#xff0c;并创建数据库执行建表sql 配置文件bugbug 1&#xff1a;Failed to instantiate type ch.qos.logback.classic.db.DBAppenderbug信息&#xff1a;解决&#xff1a; bug2: DBAppender cannot function…

windows SDK编程 --- 第一个程序

一、基础知识 1.Unicode 和 ANSI 在 Windows 编程中&#xff0c;Unicode 和 ANSI 是两种不同的字符编码方法&#xff0c;它们用于定义如何在计算机中表示和存储字符数据。 ANSI ANSI&#xff08;American National Standards Institute&#xff09;编码是一种基于单字节的字符…

最新视频理解大模型之MiniGPT4-video

前言 随着大模型的爆火&#xff0c;多模态大模型也随之卷了起来&#xff0c;基本每隔一小段时间就会冒出一个新模型。 今天给大家带来一个最新发现的关于视频理解的多模态大模型。 它的名字是MiniGPT4-video&#xff0c;可以看的出来其是MiniGPT4的一个分支&#xff1b;Mini…

vue3实现时钟效果

鼬鼬鼬鼬鼬被提需求了&#xff01;&#xff01;&#xff01; 产品&#xff1a;你学什么的&#xff1f; 我&#xff1a;跟CV有点关系 产品&#xff1a;control C加control V是吧 我&#xff1a;对对对 效果 时间实时变化&#xff1a; 页面部分 <template><div clas…

开源博客项目Blog .NET Core源码学习(14:App.Hosting项目结构分析-2)

开源博客项目Blog的前台页面&#xff08;如下图所示&#xff09;的控制器类保存在App.Hosting项目的Controllers文件夹内&#xff0c;页面保存在Views文件夹内&#xff0c;网页中使用的图标、js、css文件等保存在wwwroot文件中。 前台各个页面、Controller文件夹中的控制器类及…

Vue2电商前台项目(三):完成Search搜索模块业务

目录 一、请求数据并展示 1.写Search模块的接口 2.写Vuex中的search仓库&#xff08;三连环&#xff09; 3.组件拿到search仓库的数据 用getters简化仓库中的数据 4.渲染商品数据到页面 5.search模块根据不同的参数获取数据展示 &#xff08;1&#xff09;把派发action…

【c 语言】函数前面的返回类型

&#x1f388;个人主页&#xff1a;豌豆射手^ &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 &#x1f917;收录专栏&#xff1a;C语言 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共同学习、交流进步&…