【C++】哈希的应用 -- 位图

news2024/11/25 12:44:06

文章目录

  • 一、位图的引入
  • 二、位图的实现
  • 三、bitset
  • 四、位图的应用
  • 五、哈希切割

一、位图的引入

我们通过一道面试题来引入位图:

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

常规的解题思路是排序 + 二分,或者将数据插入到 unordered_map/unordered_set,然后进行查找;但是这两个方法在这里都不行,因为数据量太大了,内存中存放不下

1G空间大约有10亿字节,这里有40亿个整数,每个整数4个字节,那么一共就是160亿个字节,换算过来大约为16G,而我们的内存空间一般都是4G;如果我们要使用排序+二分,那么就必须开辟一个16G大小的整形数组,这显然是做不到的;而如果排序+二分不行,哈希表就更不行了,因为哈希表中每个桶中还要存放一个指针来指向下一个节点,空间消耗更大。

常规思路不行,我们就换一种思路 – 题目只要求我们判断一个数在不在,并没有其他要求,所以我们完全不用将这些数存储下来,只需要对它们进行标记即可;而要标记一个数只需要一个比特位,如果二进制比特位为1,代表存在,为0代表不存在。

所谓位图,就是用比特位来存放某种状态,适用于在海量数据中判断某一数据是否存在的场景;实际上位图是哈希表直接映射法的一种变形。


二、位图的实现

在有了具体思路之后,位图的实现就变得很简单了;一般来说,对于位图我们只需要提供如下三个接口即可:

  • set:用于将某一数值对应的比特位置1,即标记 (插入) 数据;
  • reset:用于将某一数值对应的比特位置0,即取消标记 (删除);
  • test:用于测试某一数值对应的比特位是否为1,即查找数据。

代码实现如下:

#pragma once
#include <vector>
using std::vector;

namespace thj {
	template<size_t N>
	class bitset {
	public:
		bitset() {
			_bs.resize(N / 8 + 1, 0);
		}

		void set(size_t x) {
			size_t i = x / 8;
			size_t j = x % 8;
			_bs[i] |= (1 << j);
		}

		void reset(size_t x) {
			size_t i = x / 8;
			size_t j = x % 8;
			_bs[i] &= ~(1 << j);
		}

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

	private:
		vector<char> _bs;
	};
}

其中,模板参数 N 是给定的 数据的范围 (特别注意这里N不是数据的个数),因为C++中最小的数据类型是 char,占一个字节的空间,而一个字节中有8个比特位,可以标识8个元素,所以在构造函数中我们将 vector resize 到 N/8+1 即可,这里加1是因为 C++ 中的除法是整数除法,即直接舍弃余数,所以我们需要多开辟一个字节的空间。

注:我们也可以将 vector 的数据类型定义为 int,这样我们开辟空间时 reseize 到 N/32+1 即可。

对于 set、reset 和 reset 函数,目标值 x/8 可以得到 x 应该被映射到哪个下标,即第几个 char,x%8 可以得到 x 应该被映射到该下标的第几个比特位,然后再将对应下标的对应比特位置1或置0即可。image-20230411191429762

有了位图之后,我们就可以解决上面的面试题了 – 由于题目中只说明了数据是无符号整数,而并没有给出具体的数据范围,所以我们可以将 N 定义为 -1 (有符号的 -1 等于无符号的最大值,参考 string 的 npos),然后我们只需要将这 40 亿个元素依次进行 set,最后对目标元素进行 test 即可。

注:无符号数的最大值大约等于42亿9千万,也就是说一共需要这么多个比特位来进行标记,换算过来大约5亿字节,而1G内存大约有10亿字节,所以位图最多占用512M左右的内存,这是现在的一般计算机能够做到的。image-20230411192337426


三、bitset

C++ 中其实也提供了类似于位图这样的东西,只是 C++ 把它叫做位的集合 – bitset,它的功能比我们自己模拟实现的要更加丰富,不过主要功能比如 set、reset 和 test 都是一样的。image-20230411192807040

image-20230411192839772


四、位图的应用

位图主要应用于如下几个方面:

  1. 快速查找某个数据是否在一个集合中;
  2. 排序和去重;
  3. 求两个集合的交集、并集;
  4. 操作系统中磁盘块标记;

对于快速查找某个数据是否在一个集合中,我们上面已经给出了例子,这里我们再给出它的一个变形题:

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

我们发现,使用传统的位图并不能解决这个问题,因为位图只能表示在或不在,并不能表示某个数出现了几次;而位图只能表示在或不在是因为位图中一个数据只用一个比特位表示,而一个比特位只能标识两种状态,那么我们可以将两个位图合在一起,使用两个比特位来标识一个数据,而两个比特位一共可以标识四种状态,我们取其中三种即可:

  • 00:不在;
  • 01:出现一次;
  • 10:出现两次及以上。

代码实现如下:

namespace thj {
	template<size_t N>
	class two_bitset {
	public:
		void set(size_t x) {
			int flag1 = _bs1.test(x);
			int flag2 = _bs2.test(x);

			00:第一次插入,置为01
			if (!flag1 && !flag2)
			{
				_bs2.set(x);
			}

			//01:第二次插入,置为10
			else if (!flag1 && flag2)
			{
				_bs1.set(x);
				_bs2.reset(x);
			}
			//10:第三次及以后插入,不动
			else {}
		}

		bool test(size_t x) {
			//只有01表示只出现一次,返回true
			if (!_bs1.test(x) && _bs2.test(x)) return true;
			return false;
		}

	private:
		std::bitset<N> _bs1;
		std::bitset<N> _bs2;
	};
}

image-20230411201230965

注意:这里题目只说了给100亿个整数,而并没有给出数据的范围,所以我们还是需要将位图的范围定义为无符号数的最大值的,上面将N给为100只是为了方便测试。

位图应用再变形:

1个文件有100亿个int,1G内存,设计算法找到出现次数不超过2次的所有整数?

这道题和上面求出现一次的数字的思路其实是一样的,只是这里我们要将出现次数为 0次、1次、2次、3次及以上都标识出来而已,所以需要将状态 11 利用起来;这里我就不再给出代码实现,大家可以自己尝试着实现一下。


对于排序和去重,我们可以将待排序的数据按照某种方式转换成二进制位 (比如上面的除和模),然后使用位图来表示这些数据;最后遍历位图,将所有为1的二进制位按照相同的方式转换为对应的数据输出即可;同时,由于位图只能表示存在与不存在两种状态,所以位图天生具有去重功能。

注意:位图适用于数据范围比较集中的场景,如果数据范围比较分散,则应该考虑使用其他的数据结构来实现排序和去重的功能,比如 set 和 map。


对于求两个集合的交集、并集,我们还是以面试题为例:

给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件交集?

这道题的思路很简单,我们可以先将第一个文件中的数据全部映射到位图中,然后再遍历取出第二个文件中的数据来进行 test 即可,但是这样可能会得到许多重复的结果;所以我们也可以分别将两个文件中的数据映射到两个位图中,然后遍历取出某一个位图中的数据与另一个位图进行 test。


对于操作系统磁盘块标记来说,在操作系统中的文件系统中,文件系统会将磁盘上的空间划分为一个个固定大小的块,每个块都有一个对应的位图位;位图中为0的位表示该块是空闲的,为1的位表示该块已经被分配给某个文件或目录;

当文件系统需要分配一个新的块时,可以在位图中查找第一个为0的位,将其设置为1,并将该块分配给文件;当文件系统需要释放一个块时,可以将该块对应的位图位设置为0,表示该块变为了空闲块,可以被重新分配给其他文件或目录。


五、哈希切割

除了位图,在面试时还常考下面这种题:

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

和上面的题不一样,这道题不能使用位图来解决,因为我们不知道相同IP最多会出现多少次,所以无法确定使用多少个比特位来标识一个数据;

那么既然100G太大内存放不下,我们能不能将这个文件平均分成100份小文件,这样每个文件只有1G大小,此时再依次放进 map 中进行统计呢?答案是也不行,因为再统计下一个小文件之前我们需要将前一个文件的统计结果即 map 中的数据情况,否则还是有可能因为 map 中存放的数据过多导致内存不足,但这样就会导致统计的次数不准,因为我们不能保证相同的IP全部被划分到同一个子文件中去;

正确的解决办法是进行哈希切割 – 先使用字符串哈希函数将IP地址转化为整形,然后再使用除留余数法将100G文件中的IP地址划分到不同的小文件中:

size_t Ai = HashFunc(IP) % 100;  //100为小文件的个数

经过哈希切割后,相同的IP一定会被划分到同一个小文件中,因为相同IP结果字符串哈希函数转换得到的整数是相同的,那么模出来的小标位置也是相同的;但是不同的IP也可能会被划分到同一文件中,因为会发生哈希冲突;并且划分的结果有两种:

  1. 子文件中有多种不同的IP地址,但是子文件大小在1G左右,说明这些IP地址出现的次数不多,此时我们可以直接使用 map 统计出这些IP地址的数量;(所有相同的IP地址一定会出现在同一个子文件中)
  2. 子文件中有多种不同的IP地址,但是子文件非常大,说明这些IP地址中的某一个/某几个IP地址出现次数非常多,此时 map 统计不下,我们可以换一种字符串哈希函数继续对这个子文件进行哈希切割,即递归子问题解决

最终出现次数最多的那个IP地址会被全部映射到某一个子文件中,我们对该子文件使用 map 进行统计可以得到其出现的次数。


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

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

相关文章

ChatGPT想干掉测试人员,做梦去吧

很多人都发现ChatGPT可以做一些代码相关的工作&#xff0c;不仅可以写一些测试用例和自动化脚本&#xff0c;还可以做一定量的调优&#xff0c;于是就开始担忧起来&#xff0c;到哪天我的测试工作会不会被ChatGPT这个工具给取代了&#xff1f; 1. ChatGPT目前对哪些东西会有冲击…

Java:Arrays类

1、Arrays是啥&#xff1f; 数组操作工具类&#xff0c;专门用于操作数组元素的。 2.Arrays类的常用API 方法说明public static String toString(类型[] a)对数组进行排序public static void sort(类型[] a)对数组进行默认升序排序public static <T> void sort(类型[]…

图像分割——交叉熵损失

一、前言 写这篇博客的目的主要有两点&#xff0c;首先一点就是&#xff0c;以为对于交叉熵学过就会了&#xff0c;当初笔记也没有详细写过&#xff0c;但今天看论文发现里面的公式没有看懂才发现自己了解的还不够&#xff0c;平时用也是直接用的框架&#xff0c;原来一直认为会…

Java基础--数据结构

阅读目录 目录 数据结构 Java 集合框架 List Set Map 数据结构 Java工具包提供了强大的数据结构。在Java中的数据结构主要包括以下几种接口和类&#xff1a; 枚举&#xff08;Enumeration&#xff09;、位集合&#xff08;BitSet&#xff09;、向量&#xff08;Vector&a…

进阶C语言:程序环境和预处理

有关C语言的知识马上就要结束了&#xff0c;在学完了前面的基础之上我们就来深究一下程序底层的逻辑&#xff0c;关于程序的预处理编译指令&#xff0c;话不多说&#xff0c;我们直接开始&#xff1a; 目录 1.程序的翻译环境和执行环境 2. 详解编译链接 2.1翻译环境 2.2编译…

IT培训有靠谱的机构吗,长什么样的?

关于IT培训的问题&#xff0c;网上有一大波劝退的声音&#xff1a;现在的IT越来越卷&#xff0c;高校计算机专业毕业生每年那么多&#xff0c;作为小白转行的你竞争力又在哪里呢&#xff1f;而且去年互联网大厂那么多裁员&#xff0c;还有大幅度降薪等等&#xff0c;IT行业已经…

通达信破底翻选股公式,用缠论底分型进行优化

上次在写《通达信破底翻形态选股公式&#xff0c;选出破底之后再翻回的股票》这篇文章时&#xff0c;编写破底翻选股公式就考虑使用缠论底分型&#xff0c;但是底分型的包含关系较为复杂&#xff0c;不容易处理&#xff0c;只能暂时搁置&#xff0c;采用了一种简单的方式&#…

【PyTorch】第九节:Softmax 函数与交叉熵函数

作者&#x1f575;️‍♂️&#xff1a;让机器理解语言か 专栏&#x1f387;&#xff1a;PyTorch 描述&#x1f3a8;&#xff1a;PyTorch 是一个基于 Torch 的 Python 开源机器学习库。 寄语&#x1f493;&#xff1a;&#x1f43e;没有白走的路&#xff0c;每一步都算数&#…

低延迟流式语音识别技术在人机语音交互场景中的实践

美团语音交互部针对交互场景下的低延迟语音识别需求&#xff0c;提出了一种全新的低出字延迟流式语音识别方案。本方法将降低延迟问题转换成一个知识蒸馏过程&#xff0c;极大地简化了延迟优化的难度&#xff0c;仅通过一个正则项损失函数就使得模型在训练过程中自动降低出字延…

靶机精讲之Holynix

找不到ip 就设置两个网络适配器 再添加一个NAT 主机发现 nmap扫描 端口扫描 UDP扫描 服务扫描 脚本扫描 拒绝服务攻击 sql注入 枚举 web渗透 sql注入 证明有注入 sql注入语句 语句 ‘ or 11 --&#xff08;空格&#xff09; 目录结构像有文件包含 有报错但无法利用 调用系统…

从零开始学架构-计算高性能

一、概述 高性能是每个程序员的追求&#xff0c;无论做一个系统、还是写一组代码&#xff0c;都希望能够达到高性能的效果。而高性能又是最复杂的一环&#xff0c;磁盘、操作系统、CPU、内存、缓存、网络、编程语言、数据库、架构等&#xff0c;每个都可能影响系统的高性能&…

ChatGPT API接口使用+fine tune微调+prompt介绍

目录1 接口调用1.1 生成key1.2 接口功能1.2.1 图片生成 (image generation)1.2.2 对话(chat)1.2.3 中文纠错 (Chinese Spelling Correct)1.2.4 关键词提取 &#xff08;keyword extract)1.2.5 抽取文本向量 (Embedding)1.2.6 微调 (fine tune)2 如何写好prompt2.1分类任务2.2 归…

工业智能网关应用场景:高层楼宇智慧消防解决方案

随着城市化建设的飞速发展&#xff0c;人员聚集与土地资源稀缺的矛盾越来越明显。为了让有限的空间满足更多人的居住需求&#xff0c;高层楼宇越来越多&#xff0c;对于安全消防形成更大的挑战。 基于物联网和云计算平台的智慧消防在消防管理、火灾报警和实时监管方面发挥越来…

java内部类入门(接口)

我有一个玩具狗&#xff0c;有一个接口用于启动它&#xff0c;按照传统方法就是写一个类并实现该接口&#xff0c;且该类只使用一次&#xff08;在启动时使用&#xff0c;后面再不使用&#xff09; 但是如果我有一堆玩具&#xff0c;我每个玩具都要去写一个类来实现start这个接…

GPT-3.5还没研究明白,GPT-4又来了,chatGPT会进化成什么样?

基于GPT-3.5的chatGPT热度才稍稍减退没多久&#xff0c;GPT-4又来了&#xff0c;文新一言的发布会也槽点满满&#xff0c;差距似乎越来越大了。 chatGPT到底厉害在哪&#xff1f;为什么突然就爆火了呢&#xff1f; 它的爆火&#xff0c;一方面&#xff0c;和它的出现形态有关…

代码随想录第18天 | 530.二叉搜索树的最小绝对差 501.二叉搜索树中的众数 236. 二叉树的最近公共祖先

530.二叉搜索树的最小绝对差 var getMinimumDifference function (root) {//中序遍历法&#xff1a;左中右let res []if (!root) return res;const st [root] //栈&#xff0c;pop(),push()while (st.length) {let x st.pop()if (!x) {res.push(st.pop().val)continue}if (…

Linux环境下搭建composer私服及memory_limit问题

Composer是 PHP项目中用来管理依赖&#xff08;dependency&#xff09;关系的工具&#xff0c;允许声明项目所依赖的代码库 &#xff0c;然后在项目的某个目录中(默认是vendor目录) 中安装相关的依赖包。 在介绍如何安装私服之前&#xff0c;我们先熟悉下 composer 相关 compo…

对话框与子窗口控件(写给大忙人看的快速复习掌握)

对话框与子窗口控件&#xff08;写给大忙人看的快速复习掌握&#xff09;1、对话框的概念2、控件的概念我更喜欢称控件为预定义的窗口类3、我们一步一步写代码熟悉常用的预定义的窗口类3.1 什么叫模板呢&#xff1f;3.2 什么是资源文件4、消息处理函数&#xff08;有这么几个消…

护眼灯哪些牌子好?2023护眼灯品牌推荐

护眼灯就是保护眼睛的&#xff0c;很多人长时间工作和学习&#xff0c;主要还是光的刺激和错误的坐姿&#xff0c;会引起眼睛的近视&#xff0c;导致视觉疲劳的主要原因就是灯光的频闪&#xff0c;而护眼灯就能很好减少频闪。 特别是青少年们的视力发育为成熟&#xff0c;视力…

使用Sentieon加速甲基化WGBS数据分析

全基因组甲基化测序(WGBS)是一种研究DNA甲基化的方法&#xff0c;以全面了解在基因组水平上的表观遗传变化。在进行WGBS数据分析时&#xff0c;通常需要使用专门的比对工具&#xff0c;因为这些工具需要能够处理亚硫酸盐转化后的数据。 以下是四个不同的WGBS比对分析流程&…