C++STL剖析(十)—— 位图(bitset)

news2024/11/28 20:43:13

文章目录

  • 1. 位图的介绍
  • 2. 位图的概念
  • 3. 位图的实现
    • 🍑 构造函数
    • 🍑 设置指定位
    • 🍑 清除指定位
    • 🍑 获取指定位的状态
    • 🍑 打印函数
  • 4. 总结

1. 位图的介绍

在介绍位图之前先来看一道面试题吧

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

对于判断一个数是否在某一堆数中,主要有以下方法:

  • 将这一堆数插入到 unordered_set/set 容器中,然后调用 find 函数判断该数是否在这一堆数中。
  • 将这一堆数进行外排序,然后通过二分查找的方法判断该数是否在这一堆数中。

对于上面两种方法,第一种的时间复杂度是 O ( N l o g N ) O(NlogN) O(NlogN),第二种的时间复杂度是 O ( N ) O(N) O(N)

但是有一个问题,题目给的是 40 亿个无符号的整数,如果把这些数全部加载到内存当中,那么将会占用 16G 的空间,空间消耗是很大的。因此,上面这两种方法都是不可行的。

那么怎么来解决呢?很简单,我们可以用位图(bitset)来解决!

对于数据是否在给定的整形数据中,只需要判断在或者不在,刚好是两种状态,那么可以使用一个 二进制比特位 来代表数据是否存在的信息,如果二进制比特位为 1,代表存在;如果比特位为 0,代表不存在。

如下图所示,对于 arr 集合里面的数据,我们只需要用 3 个字节(24 个 bit 位)的大小即可表示:

在这里插入图片描述

那么对于 40 亿个无符号的整型数据来说,无符号整数总共有 2 32 2^{32} 232 个,因此记录这些数字就需要 2 32 − 1 2^{32-1} 2321 个比特位(比特位是从 0 开始的,所以要减 1),也就是大约 500M 的内存空间,内存消耗大大减少。

2. 位图的概念

所谓位图,就是用每一位来存放某种状态,适用于海量数据,数据无重复的场景。通常是用来判断某个数据存不存在的。

3. 位图的实现

位图的定义需要一段连续的物理空间,所以可以拿 vector 来存储,另外,对于位图的存储,我们是不能控制 bit 位的,但是我们可以控制 字节,那么就可以把 char 或者 int 存在数组里面去。

假设数组里面存储的是 char,那么 1 个 char 就代表 8 个 bit 位。

位图的类如下:

// N个比特位的位图
template<size_t N>
class BitSet
{
public:
	// 构造函数
	BitSet();

	// 设置指定位(将x映射的位标记成1)
	void set(size_t x);

	// 清空指定位(将x映射的位标记成0)
	void reset(size_t x);

	// 获取指定位的状态
	bool test(size_t x);
	
	// 容纳的比特位的个数
	size_t size()
	
	//打印函数
	void Print()
private:
	vector<char> _bits; // vector数组里面存的是一个char类型
};

🍑 构造函数

在构造位图时,我们需要根据所给位数 N,创建一个 N 位的位图,并且将该位图中的所有位都初始化为 0。

但是一个 char 有 8 个比特位,因此 N 个位的位图就需要用到 N / 8char 类型,但是实际我们所需的 char 个数是 N/8+1,为什么呢?因为所给非类型模板参数 N 的值可能并不是 8 的整数倍。

举个例子,当 N 为 10 的时候,那么 10 / 8 = 1 相当于只开了 1 个 char 类型,那如果我去访问第 9 个 和 第 10 个 bit 位的话,不就越界了吗?

所以要 N/8+1,我知道有人肯定会担心浪费空间,我想说的是,当 N 是 8 的倍数时,可以被整除,那么此时也就才浪费 1 个 char 也就是 8 个 bit 位而已,所以无伤大雅。

代码示例

// 构造函数
BitSet()
{
	// +1保证了足够的比特位,最多浪费8个
	_bits.resize(N / 8 + 1, 0);
}

🍑 设置指定位

set 接口就是用来把 x 映射的位标记成 1,并且不能影响其它比特位,那么我怎么知道 x 映射的比特位在哪儿呢?

很简单,方法如下:

  • 先用 x / 8 = i 计算 x 应该存储在数组的第 i 个 char 对象中
  • 然后再用 x % 8 = j 计算 x 应该存储在第 i 个 char 中的第 j 个比特位上
  • 最后将第 i 个 char 对象的第 j 个比特位设置为 1 即可

前两步很简单,对于第三步,如何设置呢?

很简单,先将数字 1 左移 j 位后,再和第 i 个 char 对象也就是 _bits[i] 进行 或运算 即可

举个例子,我们现在要把数字 19 映射在位图上。

第一步,先用 19 / 8 = 2 得出要存在数组的第 2 个 char 对象中。

第二步,再用 19 % 8 = 3 得出要存在第 3 个比特位上

第三步,先将数字 1 左移 3 位得到:1 << 3 = 00001000 = 8,然后再和 bits[2] 进行 或运算 得出结果:

在这里插入图片描述

代码示例

// 设置指定位(将x映射的位标记成1)
void set(size_t x)
{
	// x映射的比特位在第几个char对象
	size_t i = x / 8;

	// x在char第几个比特位
	size_t j = x % 8;

	// 先左移,再进行或运算
	_bits[i] |= (1 << j);
}

🍑 清除指定位

reset 接口就是用来把 x 映射的位标记成 0,并且不能影响其它比特位。

很简单,方法如下:

  • 先用 x / 8 = i 计算 x 应该存储在数组的第 i 个 char 对象中
  • 然后再用 x % 8 = j 计算 x 应该存储在第 i 个 char 中的第 j 个比特位上
  • 最后将第 i 个 char 对象的第 j 个比特位设置为 0 即可

前两步很简单,对于第三步,如何设置呢?

很简单,先将数字 1 左移 j 位后,然后再对其进行 按位取反,最后再和第 i 个 char 对象也就是 _bits[i] 进行 与运算

我们还是举个例子,我们现在要把数字 19 从映射的位置上清除掉。

第一步,先用 19 / 8 = 2 得出要存在数组的第 2 个 char 对象中。

第二步,再用 19 % 8 = 3 得出要存在第 3 个比特位上

第三步,先将数字 1 左移 3 位得到:1 << 3 = 00001000 = 8,然后再把数字 8 进行按位取反得到:~8 = 11110111,然后再和 bits[2] 进行 与运算 得出结果即可。

在这里插入图片描述

注意:因为在设置指定位的时候,数字 19 已经被存储在 bits[2] 上了,所以 bits[2] 的比特位不是全为 0 哦!

代码示例

// 清空指定位(将x映射的位标记成0)
void reset(size_t x)
{
	// x映射的比特位在第几个char对象
	size_t i = x / 8;

	// x在char第几个比特位
	size_t j = x % 8;

	// 先左移,再按位取反,最后进行与运算
	_bits[i] &= (~(1 << j));
}

🍑 获取指定位的状态

test 用来获取位图中指定的位的状态,要么是 1,要么是 0。

很简单,方法如下:

  • 先用 x / 8 = i 计算 x 应该存储在数组的第 i 个 char 对象中
  • 然后再用 x % 8 = j 计算 x 应该存储在第 i 个 char 中的第 j 个比特位上
  • 最后判断第 i 个 char 对象的第 j 个比特位的状态
    • 如果是 0,说明该比特位没有被设置
    • 如果是非 0,说明该比特位被设置

前两步很简单,对于第三步,如何判断呢?

很简单,先将数字 1 左移 j 位后,再和第 i 个 char 对象也就是 _bits[i] 进行 与运算

我们还是举个例子,假设数字 19 已经被映射到位图上去了,我们现在要判断数字 19 在不在上面。

第一步,先用 19 / 8 = 2 得出要存在数组的第 2 个 char 对象中。

第二步,再用 19 % 8 = 3 得出要存在第 3 个比特位上

第三步,先将数字 1 左移 3 位得到:1 << 3 = 00001000 = 8,然后再和 bits[2] 进行 与运算 得出结果即可。

在这里插入图片描述

代码示例

// 获取指定位的状态
bool test(size_t x)
{
	// x映射的比特位在第几个char对象
	size_t i = x / 8;

	// x在char第几个比特位
	size_t j = x % 8;

	// 先左移,再进行与运算,最后直接返回与运算的结果即可
	return _bits[i] & (1 << j);
}

🍑 打印函数

最后可以实现一个打印函数 Print,用来检查我们上述代码的正确性。

对于 Print 函数的实现,也很简单,只需要遍历位图所包含的比特位进行打印即可。在打印位图的过程中可以顺便统计位图中位的个数 count,然后将 count 与我们传入的非类型模板参数 N 进行比较,可以判断位图大小是否是符合我们的预期。

代码示例

// 容纳的比特位的个数
size_t size()
{
	return N;
}

//打印函数
void Print()
{
	int count = 0;
	size_t n = _bits.size();

	// 先打印前n-1个数
	for (size_t i = 0; i < n - 1; i++)
	{
		for (size_t j = 0; j < 8; j++)
		{
			if (_bits[i] & (1 << j)) //该位被设置
				cout << "1";
			else //该位未被设置
				cout << "0";
			count++;
		}
	}

	// 再打印最后一个数的前(N%8)位
	for (size_t j = 0; j < N % 8; j++)
	{
		if (_bits[n - 1] & (1 << j)) //该位被设置
			cout << "1";
		else //该位未被设置
			cout << "0";
		count++;
	}
	cout << "\n" << count << endl; //打印总共打印的位的个数
}

然后我们测试一组数据

在这里插入图片描述

测试第二组数据

在这里插入图片描述

4. 总结

其实对于位图在 C++ 库里面是已经实现好了的:位图文档,它是 STL 的一个模板类,它的参数是整形的数值,使用位的方式和数组区别不大,相当于只能存一个位的数组。

在这里插入图片描述

以及各种常用接口

在这里插入图片描述

主要的功能如下:

在这里插入图片描述

最后,说几个位图的应用场景:

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

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

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

相关文章

【网络原理2】---TCP协议的格式

传输层重点协议TCP 协议TCP 协议段格式TCP内部的工作机制1. 确认应答2.超时重传TCP 协议 TCP 协议相对于 UDP 是复杂不少的。 在网络编程这里已经讲了 TCP 的特点&#xff1a; 有链接 可靠传输 面向字节流 全双工 可靠传输 是 TCP内部的机制&#xff0c;和编码关系不大&#x…

[oeasy]python0083_十进制数如何存入计算机_八卦纪事_BCD编码_Binary_Coded_Decimal

编码进化 回忆上次内容 上次 研究了 视频终端的 演化 从VT05 到 VT100从 黑底绿字 到 RGB 24位真彩色形成了 VT100选项 从而 将颜色 数字化 了 生活中我们更常用 10个数字 但是 计算机中 用二进制 日常计数的十进制数 是如何存储进计算机的呢?&#x1f914; 从10进制到2进…

Java学习笔记-03(API阶段-2)集合

集合 我们接下来要学习的内容是Java基础中一个很重要的部分&#xff1a;集合 1. Collection接口 1.1 前言 Java语言的java.util包中提供了一些集合类,这些集合类又称之为容器 提到容器不难想到数组,集合类与数组最主要的不同之处是,数组的长度是固定的,集合的长度是可变的&a…

思科网络部署,(0基础)入门实验,超详细

♥️作者&#xff1a;小刘在C站 ♥️个人主页&#xff1a;小刘主页 ♥️每天分享云计算网络运维课堂笔记&#xff0c;努力不一定有收获&#xff0c;但一定会有收获加油&#xff01;一起努力&#xff0c;共赴美好人生&#xff01; ♥️夕阳下&#xff0c;是最美的绽放&#xff0…

如何让一起打拼的员工有持续的动力

我们的工作都要靠团队的每一个人努力&#xff0c;如何持续让老员工也能有持续的动力完成任务是我们非常重要的管理目标。 方法一&#xff1a;提供稀缺的学习机会。很多企业的培训都是针对新员工或者管理层的&#xff0c;容易让老员工意识不到自己应该学习&#xff0c;接触不到新…

Echarts 雷达图设置拐点大小和形状,tooltip后文字不居中对齐

第017个点击查看专栏目录Echarts的雷达图的拐点大小和形状是可以设置的&#xff0c;在series中设置symbol 相应的属性即可。 使用tooltip的时候&#xff0c;默认状态文字是居中对齐的&#xff0c;不好看。需要在tooltip属性中设置一下&#xff0c;如图所示&#xff0c;效果比较…

记录robosense RS-LIDAR-16使用过程4

一、时隔一个月&#xff0c;再次记录激光雷达的使用&#xff0c;一个月不碰生疏了好多&#xff0c;如鲠在喉&#xff0c;先来个基本操作熟悉一下找找感觉。连接在线雷达&#xff1a;https://github.com/RoboSense-LiDAR/rslidar_sdk/blob/main/doc/howto/06_how_to_decode_onli…

selenium--验证码识别,一文教会你回答面试官

相信大家在日常划水&#xff0c;培训&#xff0c;工作中都遇到这样的问题&#xff0c;验证码怎么处理&#xff1f;也有一些面试官会这么问。这里大致的说说&#xff0c;最常见的处理方式。1、万能验证码&#xff1a;所谓的万能验证码也就是找开发固定一个验证码&#xff0c;比如…

jenkins下配置maven

1. 先在jenkins服务器上安装maven 下载-解压-重命名-启动 [rootVM-0-12-centos local]# wget https://mirrors.aliyun.com/apache/maven/maven-3/3.9.0/binaries/apache-maven-3.9.0-bin.tar.gz [rootVM-0-12-centos local]# tar xf apache-maven-3.9.0-bin.tar.gz [rootVM-0…

嵌入式ARM设计编程(一) 简单数据搬移

文章和代码已归档至【Github仓库&#xff1a;hardware-tutorial】&#xff0c;需要的朋友们自取。或者公众号【AIShareLab】回复 嵌入式 也可获取。 一、实验目的 熟悉实验开发环境&#xff0c;掌握简单ARM汇编指令的使用方法。 二、实验环境 硬件&#xff1a;PC机 软件&am…

【招聘】永善县社会科学界联合会招办公室文秘人员2名

【招聘】永善县社会科学界联合会招办公室文秘人员2名 因工作需要&#xff0c;根据《永善县人力资源和社会保障局关于做好2021年公益性岗位开发管理的通知》(永人社发〔2020〕34号)文件要求&#xff0c;现面向社会公开招聘公益性岗位工作人员&#xff0c;现通告如下&#xff1a…

锐捷(十五)mpls vxn跨域optionc场景

一 实验拓扑二 实验需求ce1和ce2为两个分公司&#xff0c;要求两个分公司之间用mpls vxn 进行通信&#xff0c;组网方式是optionc。三 实验分析optionc在转发平面上有点难理解&#xff0c;有一些关键点需要注意&#xff0c;大家点击链接可以参考我上篇发过的一个文章&#xff1…

前端md5加盐加密

为什么需要加密 前端在进行用户登录时&#xff0c;密码的传输如果使用明文&#xff0c;在报文被拦截之后即可直接获取传输的数据&#xff0c;明文密码被拦截会十分危险&#xff0c;因此在传输密码前时常对密码进行加密。 MD5 MD5的作用是让大容量信息在用数字签名软件签署私…

植物大战 List——C++

这里写目录标题vector和stirng的细节对于stringlist的使用list的迭代器反向迭代器构造函数关于list::sort的排序uniquelist的底层模拟实现结点类的实现迭代器模拟实现list实现插入的实现迭代器失效inserterase析构函数拷贝构造赋值构造函数vector和stirng的细节 复习vector的深…

全网详细总结com.alibaba.fastjson.JSONException: syntax error, position at xxx常见错误方式

文章目录1. 复现问题2. 分析问题3. 解决问题4. 该错误的其他解决方法5. 重要补充1. 复现问题 今天在JSONObject.parse(json)这个方法时&#xff0c;却报出如下错误&#xff1a; com.alibaba.fastjson.JSONException: syntax error, position at 0, name usernameat com.aliba…

Web自动化测试——Junit5篇

文章目录一、相关依赖注入二、注解调用三、断言 Assert四、规定用例执行顺序五、高效参数化1&#xff09;单参数2&#xff09;多参数3&#xff09;文件获取参数4&#xff09;方法获取数据&#xff08;动态参数&#xff09;六、测试套件整活Junit 是一个面向 Java 语言的单元测试…

网络安全-协议爆破-xhydra

网络安全-协议爆破-xhydra 啥叫爆破呢&#xff0c;当你忘记密码了&#xff0c;一个一个去猜想的时候&#xff0c;这个东东就叫暴力破解 简称爆破&#xff0c;而且用程序的组合去一个一个试&#xff0c;时间问题&#xff0c;总有尝试到的那天 爆破前需求 也就是说满足了什么…

数据库测试的认知和分类

数据库测试的认知和分类 目录&#xff1a;导读 系统测试 集成测试 单元测试 功能测试 数据库性能 性能优化分4部分 安全测试 现在的软件系统&#xff0c;尤其是业务应用系统&#xff0c;后台都连接着一个数据库。数据库中存储了大量的数据&#xff0c;数据库的设计是否…

完整爬虫学习笔记(第一章)

文章目录前言:fu:. 爬虫概述:hotdog:原理解剖:one: 服务器渲染:two: 前端JS渲染:fire: 第一个爬虫程序案例总结前言 最近正在学习Python网络爬虫的相关知识&#xff0c;鉴于本人Python水平有限 , 对Python并无太深的理解&#xff0c;所以此文章的主要目的在于抛砖引玉&#xf…

C语言(ANSI C类型限定符)

目录 1.const(恒常性) 2.volatile 3.restrict 1.const(恒常性) 如果我们想处理一个基本类型时&#xff0c;我们可以选择传递类型变量值或类型变量的地址。但有的时候我们传入地址但不想让其修改地址上面存储的值&#xff0c;那么就可以用到const。 这个时候const的作用就到了。…