当前位置: 首页 > article >正文

bitset详解

本文旨在讲解位图的原理,以及位图有什么作用,如何实现位图。希望读完本篇文章能对小伙伴们有一定的收获!上干货!

什么是位图

位图就是bitmap的缩写,所谓bitmap,就是用每一位来存放某种状态,适用于大规模数据,该数据都是不重复的简单数据。通常是用来判断某个数据存不存在的。

C++中以bitset定义为位图,下面我们来看一下C++对位图的解释吧!

在C++中,位图以非类型的模版参数定义,简单翻译下来就是:位图存储比特位(其存储的元素只有0/1两个值)。


位图的应用:(海量数据的处理)

位图通常用于海量数据的处理!

例如如果有40亿个无符号整数,那么如何快速判断一个数是否存在与这40亿个无符号整数数中呢?

可能有的小伙伴首先想到的就是遍历一遍数组,然后判断即可!那么如果是否真的能遍历呢?对于40亿个无符号整数,其占用大约15G的空间!

40亿*4=160亿字节/1024/1024/1024=14.9 

对于40亿个数字,大约占了15G内存,大多计算机根本放不下这么大的数据!那么该如何解决呢?此时就引进了位图,位图其只用一个比特位代表一个数字!所以40亿个比特位就转化为内存大约为:40亿/8/1024/1024/1024 =0.46G,占用不到0.5G的内存,显然使用位图的方式可以解决!那么该如何使用位图呢?

我们只需要求出这个数字对应于位图的哪一位即可,如果该数字存在,那么就将改为置为1即可,否则就置为0。

对于位图,我们定义一个vector<char>的向量,每个元素为char类型的,占8个字节!我们只需要求出数字在这个vector中对应的位置即可,存在的话,将对应为设为1,下面来看一个例子!

对于数字1,5,7,15,22,在位图中就是这样存储的!

这是怎么计算的呢?下面来讲解一下:对于vector,内部放的是char类型的,因为char占8个字节,所以我们只需要将数字除一个8,就能得到在第几个字节中,然后进行取余,就可以得到在当前字节的第几个比特位!

例如15

15/8=1   15%8=7;所以15就在下标为1的那个字节中,在当前字节中的第7个比特位!其他也是同理的!


既然了解了位图的存储方式,那么我们就来简单实现一下位图这个结构吧!

实现位图

参考C++中的位图的一些功能,我们就简单实现一些位图的操作!

我们就来简单实现test(),set(),reset(),这几个函数吧!

set()函数的实现

通过观察C++文档, 我们可以得知,set的作用是将对应的比特位设置为true即为1!

下面我们就来自己实现这个set函数,因为位图的结构是我们自己定义的,那么当我们将位图的底层用vector<int>向量来进行实现的话,我们只需要将除的值和模的值都变成int的比特位即可!

下面我们就以vector<int>向量来进行实现!

		//实现set函数!
		//观察库中的set函数,我们可以发现set函数是将x对应的比特位置为1!
		void set(size_t x)
		{
			//i代表数组中的第几个下标!
			int i = x / 32;

				//j代表的是该下标的第几个比特位!
			int j = x % 32;

			//然后将数组中i位置的第j个比特位置为1即可!
			//将第几个比特位置为1,我们可以使用按位或上1即可!
			_bitset[i] |= 1 << j;
		}

将某一位设置为1,前面我的文章已经讲过了,我也不过多进行解释了,只需要将1左移j位,然后按位与上1即可!


reset()函数的实现!

我们实现库中的第二个reset函数,将pos位置为0。代码如下

//reset的作用是将x对应的位重置为0!
		void reset(size_t x)
		{
			//i代表数组中的第几个下标!
			int i = x / 32;

			//j代表的是该下标中的第几个比特位!
			int j = x % 32;

			//将第i为置为0的操作,只需要进行按位于上个(1左移x=位取反!)即可!
			_bitset[i] &= ~(1 << j);
		}
		

我们将某一位置为0只需要进行将1进行左移j位,然后取反按位与即可!!


test()函数的实现!

test()函数用于检验当前位是否被设置,如果被设置就返回true,否则返回false,即判断当前位是否为1,为1返回true,为0返回false!

bool test(size_t x)
		{
			//i代表数组中的第几个下标!
			int i = x / 32;

			//j代表的是该下标中的第几个比特位!
			int j = x % 32;

			//按位与上个1左移j位
			return _bitset[i]&(1 << j);

		}

至此位图相关函数已经实现,下面我们看一下位图的成员的实现,以及构造函数!

 

成员就是我们刚才讲的,其中vector中的类型可以换成char,long long 等类型!

对于构造函数!

因为是vector容器,其resize的参数默认是n个存储元素的大小,所以我们多开辟一个该类型的空间,防止因不能整除导致空间不够的问题!


 位图实现的所有代码

#pragma once


//自己模拟实现位图的操作!

//加上命名空间与库中的bitset进行区分
#include<vector>
namespace Zhj
{
	//非类型的模版!
	template<size_t	N>
	class Bitset
	{
	public:
		//构造函数!
		Bitset()
		{
			//对位图进行初始化!
			//为容器分配空间,因为是整形类型的容器,所以需要开辟N/32   但是可能除不尽,所以多开辟一个int的空间!
			_bitset.resize(N / 32 + 1);
		}

		//实现set函数!
		//观察库中的set函数,我们可以发现set函数是将第x为置为1!
		void set(size_t x)
		{
			//i代表数组中的第几个下标!
			int i = x / 32;

				//j代表的是该字节中的第几个比特位!
			int j = x % 32;

			//然后将数组中i位置的第j个比特位置为1即可!
			//将第几个比特位置为1,我们可以使用按位或上1即可!
			_bitset[i] |= 1 << j;
		}

		//reset的作用是将第x为重置为0!
		void reset(size_t x)
		{
			//i代表数组中的第几个下标!
			int i = x / 32;

			//j代表的是该字节中的第几个比特位!
			int j = x % 32;

			//将第i为置为0的操作,只需要进行按位于上个(1左移x=位取反!)即可!
			_bitset[i] &= ~(1 << j);
		}
		

		//检测第x位是什么!
		bool test(size_t x)
		{
			//i代表数组中的第几个下标!
			int i = x / 32;

			//j代表的是该字节中的第几个比特位!
			int j = x % 32;

			//按位与上个1左移j位
			return _bitset[i]&(1 << j);

		}
	private:
		//默认以存储int类型来构造!

		vector<int> _bitset;

	};
}

海量数据的处理

既然了解了位图,以及其实现,那么我们就来看一些关于位图的题目吧!

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

对于这道题,首先想到的肯定是使用位图,因为如果不使用位图,内存空间是肯定不够的!那么我们该如何使用位图来解决问题呢?

对于整数类型,其取值范围为0~2^32-1;对于100亿个数字,肯定是存在重复的,那么我们就需要创建两个位图,用于统计出现的次数!

位图1 s1, 位图2 s2。

我们可以这样进行统计次数!

0   0                 代表0次!

0   1                代表一次!

1   0                 代表两次即以上! 

我们只需要创建两个位图将该数字对应的位数进行统计即可!我们可以使用库中的bitset,来实现,我们自己来实现一个由两个位图构成的位图!

#include<bitset>
#include<iostream>
#include<vector>
using namespace std;



template<size_t	N>
class twobitset
{	
public:
	void set(size_t x)
	{ 
		//00  --> 01   
		if (_b1.test(x) == false && _b2.test(x) == false)
		{
			_b2.set(x);			//一次!
		}
		//01->10
		else if (_b1.test(x) == false && _b2.test(x) == true)
		{
			_b1.set(x);  //两次及以上!
			_b2.reset(x);
		}
	}

	void printonce()
	{
		for (size_t i = 0; i < N; i++)
		{
			if (_b1.test(i) == false && _b2.test(i) == true)
			{
				cout << i<<endl;
			}
		}
	}

private:
		bitset<N> _b1;
		bitset<N> _b2;
};

下面来看测试函数!

 #define  _CRT_SECURE_NO_WARNINGS 1
#include"TwoBitset.h"
#include<bitset>

int main()
{
	twobitset<100> bs;

	int a[] = { 1,51,34,5,16,7,9,8,8,9,51 };
	for (auto ch : a)
	{
		bs.set(ch);
	}
	
	bs.printonce();
	return 0;
}

运行结果如下:

确实把我们只出现一次的数字进行的打印出来,那么将100亿个整数放入,那也是可以的!


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

因为还是海量数据,所以还是使用的位图思想,我们需要创建两个位图,然后将这100亿个数字利用set函数放到位图中,然后将这两个位图的各个位置进行按位与,把按位与的结果最后利用test函数检测为1的状态,为1的就是交集!


3. 位图应用变形:1个文件有100亿个int,1G内存,设计算法找到出现次数不超过2次的所有整数。

与第一题一样,只不过需要多加一个条件即可!即出现两次的也要打印,所以我们也要记录10的存在,当两次以上的结果我们记录为11即可即可!代码如下:

#include<bitset>
#include<iostream>
#include<vector>
using namespace std;



template<size_t	N>
class twobitset
{	
public:
	void set(size_t x)
	{ 
		//00  --> 01   
		if (_b1.test(x) == false && _b2.test(x) == false)
		{
			_b2.set(x);			//一次!
		}
		//01->10
		else if (_b1.test(x) == false && _b2.test(x) == true)
		{
			_b1.set(x);  //两次!
			_b2.reset(x);
		}
		else if (_b1.test(x) == true && _b2.test(x) == false)
		{
			_b2.set(x);    //两次以上的情况!
            
		}
	}

	void prinlesstwo()//打印两次及以下!
	{
		for (size_t i = 0; i < N; i++)
		{
			if (_b1.test(i) == false && _b2.test(i) == true)
			{
				cout << i<<endl;
			}
			else if (_b1.test(i) == true && _b2.test(i) == false)
			{
				cout << i << endl;
			}
		}
	}

private:
		bitset<N> _b1;
		bitset<N> _b2;
};


 至此,位图的相关介绍已经完毕,希望读完本篇文章,能对读者有一定的收获!


http://www.kler.cn/a/274189.html

相关文章:

  • 单片机:实现数码管动态显示(0~99999999)74hc138驱动(附带源码)
  • C++23新特性解析:[[assume]]属性
  • 《三角洲行动》游戏运行时提示“缺失kernel32.dll”:问题解析与解决方案
  • 基于卷积神经网络融合Inception模块的人脸识别
  • nmap扫描优化
  • 使用 acme.sh 申请域名 SSL/TLS 证书完整指南
  • 代理IP品质对Tik Tok代理的重要性
  • Vue快速教程:如何优雅地移除数组中的特定元素?
  • 架起桥梁,畅享流通:如何使用私有Registry实现镜像跨源同步与管理
  • linux系统中的PS命令详解
  • R语言中的常用基础绘图函数 直方图,箱线图,条形图,散点图
  • 深入理解nginx的请求限速模块[下]
  • 代码随想录算法训练营 DAY 14 | 二叉树的递归遍历和迭代遍历
  • 中间件-消息队列
  • git的起源
  • JavaScript中new操作符具体干了什么
  • 【LIMS】微服务
  • 前端项目,个人笔记(三)【Vue-cli - api封装-axios使用举例】
  • c++ 自己实现一个迭代器
  • golang面试题总结
  • sparksession对象简介
  • 网页的制作
  • Linux 建立链接(ln)
  • (学习日记)2024.03.16:UCOSIII第十八节:任务的删除
  • 数据结构奇妙旅程之红黑树
  • 大语言模型(LLM)Token 概念