C++ 位图及其应用

news2024/11/19 2:26:56

前言

现实生活中,有很多场景是需要处理数据量很大的数据的,比如:
给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在这40亿个数中。

一看到这样的题,我们可能想到的就是

  1. 排序+二分查找
  2. 哈希表 / 红黑树

但是40亿明显是一个很大的数据量,不管哪个方法都不适合。

而这时,位图产生了。

文章目录

  • 前言
  • 一. 什么是位图
  • 二. 位图的实现
    • 1. 基本结构
    • 2. 数据的标记
    • 3. 数据的清除
    • 4. 数据的查找
    • 5. 测试
  • 三. 位图的应用
  • 结束语

在这里插入图片描述

一. 什么是位图

所谓位图,就是用每一位比特位来存放某种状态,适用于海量数据,数据无重复的场景。通常是用来判断某个数据存不存在的。因为比特位只有0 / 1两种状态,用来标识存在与否这种原子性的状态,再合适不过了。

第几个比特位其实类似数组的下标,但是存储的数据只是0 / 1。
比如
在这里插入图片描述
每一个方格代表一个比特位,初始为0。如果存储3,则在3位置的比特位写入1,存储7,则在7位置的比特位写入1。

所以前言的题目,就由最开始的,一个整型存储一个数,需要16G:40亿数据大约是4G,而每一个整数需要4个字节,也就是总共16G
但是如果我们使用位图,只需要一个比特位标记一个数是否存在,所以一共需要40亿个比特位,一个字节又有8个比特位,所以总共需要4GB / 8,也就是512MB
量级一下子小了不少

二. 位图的实现

虽然位图是以比特位为基本单元的,但是我们并不能直接创建比特位,不过我们可以使用char类型,其大小为1字节,8个比特位。
如果我们要存储59,那么其实是存储在59 / 8 =7,59 % 8 =3,第7个char的第3个比特位

1. 基本结构

因为我们需要随机存储,所以我们底层使用vector,内部存储char类型。同时可以使用非类型模板参数,由需求规定当前位图存储数据量的大小

代码如下:

template<size_t N>
class bitset
{
	bitset()
	{
		_bits.resize(N / 8 + 1, 0);
	}
private:
	vector<char>_bits;
};

模板参数的N,就是指定该位图存储数据大小的范围
但是因为基本类型是char,一个char有8个比特位,所以vector实际需要的char并不是N,而是N / 8+1。

2. 数据的标记

存储一个数据,我们需要将其对应的比特位置1
正如我们上述的逻辑,我们首先通过 / 8获取,要在第几个char做修改;再 %8获取修改其第几个比特位
最后我们使用或运算,就修改成功了。
因为我们要置1。
0和1,与1或运算都是1。
0和1,与0或运算都是其本身
我们只要通过左移(将1从低地址移到高地址),使得除修改位置外,其他比特位都是0,就可以只修改特定比特位
代码如下:

	//标记
	void set(size_t N)
	{
		size_t piece = N / 8;
		size_t bit = N % 8;

		_bits[piece] |= (1 << bit);
	}

3. 数据的清除

清除一个数据,我们需要将其对应的比特位置0
基本逻辑相同,也是先获取要修改的位置,使用与运算修改
0 / 1 ,与0与运算都是0
0 / 1, 与1与运算是其本身
所以我们需要将修改的比特位与0与运算,其他比特位与1与运算,只要将1左移再取反就可以了
代码如下:

	//去标记
	void reset(size_t N)
	{
		size_t piece = N / 8;
		size_t bit = N % 8;

		_bits[piece] &= (~(1 << bit));
	}

4. 数据的查找

数据的查找返回bool值,为非0就是true
大致逻辑相同,最后与1与运算即可
代码如下:

	//查找
	bool test(size_t N)
	{
		size_t piece = N / 8;
		size_t bit = N % 8;

		return _bits[piece] & (1 << bit);
	}

5. 测试

我们加一个打印的函数,如果当前位图某比特位为1,那么打印这个数

	//打印
	void Print()
	{
		for (size_t i = 0; i < N; i++)
		{
			if (test(i))
			{
				cout << i << " ";
			}
		}
		cout << endl;
	}

测试代码如下:

void test_bitset1()
{
	bitset<100> bs;
	bs.set(10);
	bs.set(11);
	bs.set(15);
	bs.set(25);
	bs.set(34);
	bs.set(7);

	bs.Print();

	bs.reset(7);
	bs.reset(34);

	bs.Print();
}

运行结果如下:
在这里插入图片描述

回到前言的问题,大致记录方式我们了解了。
但是需要注意的是,不管存储数据个数有多少,对于整型,我们都需要开整型最大值的空间,因为就算是只记录100个数据,这100个数据的大小,大的可能到42亿,小的可能到个位数。模板参数的N,是记录数据的大小范围,不是存储数据个数

可以如下设置

bitset<-1>bs;
bitset<0xFFFFFFFF>bs;

三. 位图的应用

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

首先,整数的大小范围就是0~42亿,所以100亿个整数,肯定都很多重复数据。
其次,100亿个整数,并不影响我们给位图开的空间,我们依然需要开42亿字节的位图。
但是因为我们要找只出现一次的整数,需要记录一个数出现的次数

一个思路是:我们修改位图的结构,加一个计数的变量

但是这个思路并不合适,因为位图的使用本身就是用来处理海量数据的,因为其节省了很多空间。但是如果给每个比特位添加计数,那么空间使用的成本又上去了。显然两者是相违背的

第二个思路是:我们使用两个位图,通过同一位比特位,两个位图的状态标记,区分一个数出现的次数
没有出现就是00,出现一次是01,出现多次是10。
这样我们只需要在遍历的时候,找到第二个位图是1的数据,就是只出现一次的数据

实现代码如下:

template<size_t N>
class twoBitset
{
public:
	//数据的插入
	void set(size_t n)
	{
		if (_bs1.test(n) == false && _bs2.test(n)== false)
		{
			//第一次出现
			_bs2.set(n);
		}
		else if (_bs1.test(n) == false && _bs2.test(n) == true)
		{
			//第二次出现
			_bs1.set(n);
			_bs2.reset(n);
		}
	}

	//打印
	void Print()
	{
		for (size_t i = 0; i < N; i++)
		{
			if (_bs2.test(i) == true)
			{
				cout << i << " ";
			}
		}
		cout << endl;
	}


private:
	bitset<N>_bs1;
	bitset<N>_bs2;
};
  • 测试
    在这里插入图片描述

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

有两种方法

  • 第一种方法:

其中一个文件的值映射到位图中,然后读取另一个文件的值,去位图中查找在不在,在就是交集,不在就不是交集。
但是可能有重复数据,所以我们可以在查找成功,即找到交集的数值,将其位图的比特位置0,这样后续再有重复数据也不会查找成功

  • 第二种方法:

两个文件映射到两个位图,然后遍历位图。当两个位图的比特位都是1,代表是交集,反之则不是。

第一种方法适用于文件数据较小的情况,因为方法一查找的次数是根据数据个数改变
而第二种方法不管数据个数多少,都需要查找位图范围,即整型所表示数字范围。


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

这个简单,我们只需要用两个位图就好
00表示出现0次
01表示出现1次
10表示出现2次
11表示出现3次


位图总的应用大致如下

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

结束语

位图的优点:速度快,节省空间
缺点:只能映射整型,其他类型如:浮点数,string等等不能存储映射。

本篇内容到此就结束了,感谢你的阅读!

如果有补充或者纠正的地方,欢迎评论区补充,纠错。如果觉得本篇文章对你有所帮助的话,不妨点个赞支持一下博主,拜托啦,这对我真的很重要。
在这里插入图片描述

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

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

相关文章

悬赏问答|互助社区 X3.5正式版 1.5

模板演示站: 以下3个演示地址均为本模板设置的不同界面效果,演示站已启用了我们百变百搭-APP手机版,如需体验手机版可使用手机浏览器或微信中打开: 模板演示效果地址1:http://demo.1009.com.cn/012 模板演示效果地址2:http://demo.1009.com.cn/012a 模板演示效果地址3:h…

Handler相关问题

Handler相关问题 1、设计 Handler 的初衷&#xff1f;2、一个线程有几个 Looper&#xff1f;几个 Handler&#xff1f;3、Handler 内存泄漏原因&#xff1f; 以及最佳解决方案&#xff1f;4、为何主线程可以 new Handler&#xff1f;如果想要在子线程中 new Handler 要做些什么…

docker是怎么决定容器内容存储到哪个目录的?(存储驱动决定的)(乱七八糟的)(df -Th查看目录文件系统类型、查看文件系统类型)

文章目录 docker是怎么决定容器内容存储到哪个目录的&#xff1f;docker对我/var这个目录有没有什么要求&#xff0c;比如要求它的文件系统是指定的类型如果我Docker的默认存储驱动是overlay2&#xff0c;但是我/var目录的文件系统不是overlay2&#xff0c;这没影响吗&#xff…

人工智能MINIST手写数字识别之MINIST概念

MNIST是一个简单的视觉计算数据集,它是像下面这样手写的数字图片: MNIST 每张图片还额外有一个标签记录了图片上数字是几,例如上面几张图的标签就是:5、0、4、1。 MINIST数据 MINIST的数据分为2个部分:55000份训练数据(mnist.train)和10000份测试数据(mnist.test)。这…

Doo Prime 德璞资本:初学者必看!期货是怎么交易的四大技能

期货交易是一种金融衍生品交易&#xff0c;是指在未来某个约定的时间和价格上&#xff0c;按照合约规定的标的物进行买卖的交易方式。它是一种非常重要的投资方式&#xff0c;因为它可以帮助投资者在风险管理方面更好地掌握市场。 期货的交易方式非常多样化&#xff0c;尤其是…

【51单片机】:串口通信控制LED亮灭任务

学习目标&#xff1a; 使用51单片机的串口通信&#xff0c;当串口通信助手 发送字符串 on led开启 发送字符串 off led关闭 并且串口助手实时返回 发送的字符串 学习内容&#xff08;代码&#xff09;&#xff1a; 第一种方法&#xff0c;使用数组依次判断接收到的字…

凝聚青年力量,打造数字化人才队伍

当代青年人勇于探索、敢于创新、勤于变革&#xff0c;积极承担社会责任。这与ABeam倡导的「Build Beyond As One.™」的品牌理念不谋而合。ABeam的青年员工是未来社会的中坚力量&#xff0c;也正用他们的青春能量助力ABeam在中国的发展。 01 新兴青年力量 对ABeam而言&#…

走进工厂,认识静电测试仪器——使用方法和注意事项

随着科技的不断发展,静电测试仪器越来越多地被人们所使用。但是有些人对静电测试仪的使用方法和注意事项还不是很了解。 1&#xff1a;静电测试仪器的基本知识 静电测试仪器是一种用来测量电源电压、电流和电容器的材料。通常&#xff0c;静电测试仪器由一个电阻器或一组绝缘…

R语言丨Pheatmap绘制基因表达量热图

Pheatmap绘制基因表达量热图 论文中展示基因表达量变化通常使用热图&#xff0c;今天分享一个快速绘制不同基因在各处理下表达量变化的方法&#xff0c;使用R语言中pheatmap包&#xff0c;它可以用于可视化数据集中的数值&#xff0c;以便更好地理解数据之间的关系和模式。 …

STM32单片机WIFI教室灯光控制系统人数自动灯光温度时间

实践制作DIY- GC0135-WIFI教室灯光控制系统 一、功能说明&#xff1a; 基于STM32单片机设计-WIFI教室灯光控制系统 二、功能介绍&#xff1a; 电路&#xff1a;STM32F103C最小系统板DS18B20温度传感器LCD1602显示器ESP8266WIFI模块4个红外槽型光电传感器3个LED灯多个按键蜂鸣…

Linux内核模块编程

访问【WRITE-BUG数字空间】_[内附完整源码和文档] 1 总体设计思路 Linux内核是单体式结构&#xff0c;相对于微内核结构而言&#xff0c;其运行效率高&#xff0c;但是系统的可维护性和可扩展性较差。为此&#xff0c;Linux提供了内核模块&#xff08;module&#xff09;机制&…

腾讯轻量服务器python3.6升级到python3.9.9

由于不了解linux&#xff0c;要配合宝塔的查看文件&#xff0c;这样轻松很多 用得到的2个基本命令: sudo 管理员方式运行&#xff08;我照着网上方法试几次安装都没成功&#xff0c;就是开头没加这句&#xff09; pwd 显示当前的目录 第1步 下载新python sudo wget https://ww…

用写代码的方式画图-试下PlantUML吧 | 京东云技术团队

1 序言 所谓一图胜千言&#xff0c;大家平日在工作中编写文档时&#xff0c;往往都需要画各种图来表达中心思想&#xff0c;比如流程图、时序图、UML 图&#xff0c;很多人选择使用 Axure 、PrecessOn、Diagrams&#xff08;darw.io&#xff09;、XMind、Visio、yEd、Lucidcha…

2023年企业降低云支出的小方法汇总

据悉&#xff0c;2023年全球云基础设施服务支出全年将增长23%&#xff0c;也就是说云支出会持续增长。所以企业有效降低云支出是刻不容缓的。这里就给大家汇总了一些企业降低云支出的小方法&#xff0c;希望有用。 2023年企业降低云支出的小方法汇总 1、寻找价格折扣 提前计…

计算机图形学 | 实验十一:阴影计算

计算机图形学 | 实验十一&#xff1a;阴影计算 计算机图形学 | 实验十一&#xff1a;阴影计算帧缓冲创建一个帧缓冲纹理附件渲染缓冲对象附件总结 阴影映射算法思想深度贴图渲染阴影抗锯齿 assimp库结果 华中科技大学《计算机图形学》课程 MOOC地址&#xff1a;计算机图形学&a…

分布式ID解决方案(一)数据库号段方式

一、前言 在一些简单系统中&#xff0c;我们可以直接使用数据库ID自增方式来标识和保存数据&#xff0c;但是随着系统的逐渐复杂&#xff0c;数据量的日益增多&#xff0c;我们可能需要对数据表、数据库实现分库分表。单纯的使用数据库的ID自增无法满足业务场景了&#xff0c;所…

Seata 的可观测实践

作者&#xff1a;察溯 Seata 简介 Seata 的前身是阿里巴巴集团内大规模使用保证分布式事务一致性的中间件&#xff0c;Seata 是其开源产品&#xff0c;由社区维护。在介绍 Seata 前&#xff0c;先与大家讨论下我们业务发展过程中经常遇到的一些问题场景。 业务场景 我们业务…

数据规模缩小 200 倍!指令微调高效指导大模型学习

夕小瑶科技说 原创 作者 | 智商掉了一地、Python 最近大型语言模型&#xff08;LLMs&#xff09;的指令微调备受研究人员的关注&#xff0c;因为它可以开发 LLM 遵循指令的潜力&#xff0c;使其更加符合特定的任务需求。虽然指令微调&#xff08;Instruction Tuning&#xff…

JavaEE-HTTPS的加密流程

目录 对称加密非对称加密证书的引入 对称加密 对称加密就是用同一个密钥把明文进行加密变成密文,也能把密文解密为明文. 理想状态下: 引入对称加密之后, 即使数据被截获, 由于黑客不知道密钥是啥, 因此就无法进行解密, 也就不知道请求的真实内容是啥了. 但同一时刻服务器服务…

数据库规范与SQL调优

数据库设计规范章节&#xff0c;依旧以《阿里巴巴Java开发手册》为原型进行修正和完善。 MySQL规约 (一) 建表规约 (二) 索引规约 (三) SQL规约 (四) ORM规约 (一) 建表规约 1. 【强制】 表达是与否概念的字段&#xff0c;必须使用is_xxx的方式命名&#xff0c;数据类型是…