【C++】位图

Ⅰ、bitset的介绍

位图:

  • 就是用 比特位 来标识某种状态,适用于海量数据,数据无重复的场景。通常是用来判断某个数据存不存在的。

位图的接口:

cpp 复制代码
成员函数	功能
set		设置指定位或所有位
reset	清空指定位或所有位
flip	反转指定位或所有位
test	获取指定位的状态
count	获取被设置位的个数
size	获取可以容纳的位的个数
any		如果有任何一个位被设置则返回true
none	如果没有位被设置则返回true
all		如果所有位都被设置则返回true
  • 注意:库里面的函数可以是指定位,也可以是所有位。

栗子:

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

一般思路:

  1. 排序+二分
  2. 哈希(unordered_map)

在这里,我们可以用位图解决,空间复杂度会变低。

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

比如:

位图的应用:

  • 快速查找某个数据是否在一个集合中。
  • 排序+去重。
  • 求两个集合的交集、并集等。
  • 内核中信号标志位(信号屏蔽字和未决信号集)。

Ⅱ、位图的实现

1 .整体框架

  • 我们选择用一个vector来存放数据。具体开多大的空间,可以用模板来表示。
  • 在构造函数中,用户模板传入的多大字节。其中我们把用户传的大小除以32再加1。
  • 判断多少个整形可以有这么多字节的空间,但是会浪费几个比特位,这些空间都是可以忽略的。
cpp 复制代码
class BitSet
{
public:
	BitSet()
	{
		_bs.resize(N / 32 + 1);
	}

private:
	vector<int> _bs;
};

2 .把某一位设置为1

这里有两步:

  1. 找到x所处在的那一位: 先确定这个数应该处在第几个整数(类似哈希表找存储位置),也就是index=x/32;然后通过把x对32取模,得到x在第index个整形的第pos个位置
  2. 把改为设置为1: 把1左移pos位,用这个数和第index整数进行按位或的操作
cpp 复制代码
void set(size_t x)
{
	int i = x / 32;
	int y = x % 32;
	_bs[i] |= (1 << y);
}

3 . 把某一位设置为0

两步:

  1. 找到那一位: 同上
  2. 把这位设置为0: 把1左移pos位,把这个数取反,然后和第index个整数进行按位与的操作
cpp 复制代码
void reset(size_t x)
{
	int i = x / 32;
	int y = x % 32;
	_bs[i] &= (~(1 << y));
}

4 . 判断某一位是否为1

两步:

  1. 找到那一位: 同上
  2. 把这位设置为0: 把1左移pos位,然后返回这个数和第index个整数进行按位与的的结果
cpp 复制代码
bool test(size_t x)
{
	int i = x / 32;
	int y = x % 32;
	return _bs[i] & (1 << y);
}
  • 测试:判断一个数组的元素在另一个数组是否存在。
cpp 复制代码
void Test_BitSet()
{
	int a1[] = { 5,7,9,2,5,99,5,5,7,5,3,9,2,55,1,5,6 };
	int a2[] = { 5,3,5,99,6,99,33,66 };

	BitSet<100> bs;
	for (auto e : a1)
	bs.set(e);
	for (auto e : a2)
		cout << bs.test(e) << " ";
	cout << endl;
}
  • 测试结果:可以看见,33和66不存在,打印出来就是0。

5 .例题

问题:用位图实现一个数在数组中出现了几次?(一定是3次及一下)

  • 这个题我们可以封装双位图来实现,一个位图代表个数的第一个比特位,一个位图代表个数的第二个比特
cpp 复制代码
template<size_t N>
class Two_BitSet
{
public:
	void set(size_t x)
	{
		bool bit1 = _bs1.test(x);
		bool bit2 = _bs2.test(x);
//根据对应位置是否存在,来判断是否进位
		if (!bit1 && !bit2)
		{
			_bs2.set(x);
		}
		else if (!bit1 && bit2)
		{
			_bs1.set(x);
			_bs2.reset(x);
		}
		else
		{
			_bs2.set(x);
		}
	}
	int GetCount(size_t x)
	{
		bool bit1 = _bs1.test(x);
		bool bit2 = _bs2.test(x);
		if (!bit1 && !bit2)
		{
			return 0;
		}
		else if (!bit1 && bit2)
		{
			return 1;
		}
		else if (bit1 && !bit2)
		{
			return 2;
		}
		else
			return 3;
	}
private:
	BitSet<N> _bs1;
	BitSet<N> _bs2;
};

测试:

cpp 复制代码
void Test_TwoBitSet()
{
	int a[] = { 5,7,9,2,5,99,5,5,7,5,3,9,2,55,1,5,6,6,6,6,7,9 };
	int sum = 0;
	Two_BitSet<100> bs;
	for (auto x : a)
		bs.set(x);
	for (int i = 0; i < 100; i++)
	{
		cout << bs.GetCount(i) << " ";
	}
	cout << endl;

}

在测试用例中,我使某些数,多出现了几次,所以结果就不准确了。

Ⅲ、总结

  • 位图实现起来十分的简单,应用也是很广的,可以达到节省空间的效果。
  • 但是它有一个致命的缺点就是只能处理整形数据。

整体代码:

cpp 复制代码
#pragma once
#include<vector>
using namespace std;
template<size_t N>
class BitSet
{
public:
	BitSet()
	{
		_bs.resize(N / 32 + 1);
	}
	void set(size_t x)
	{
		int i = x / 32;
		int y = x % 32;
		_bs[i] |= (1 << y);
	}
	void reset(size_t x)
	{
		int i = x / 32;
		int y = x % 32;
		_bs[i] &= (~(1 << y));
	}
	bool test(size_t x)
	{
		int i = x / 32;
		int y = x % 32;
		return _bs[i] & (1 << y);
	}
private:
	vector<int> _bs;
};
template<size_t N>
class Two_BitSet
{
public:
	void set(size_t x)
	{
		bool bit1 = _bs1.test(x);
		bool bit2 = _bs2.test(x);
		if (!bit1 && !bit2)
		{
			_bs2.set(x);
		}
		else if (!bit1 && bit2)
		{
			_bs1.set(x);
			_bs2.reset(x);
		}
		else
		{
			_bs2.set(x);
		}
	}
	int GetCount(size_t x)
	{
		bool bit1 = _bs1.test(x);
		bool bit2 = _bs2.test(x);
		if (!bit1 && !bit2)
		{
			return 0;
		}
		else if (!bit1 && bit2)
		{
			return 1;
		}
		else if (bit1 && !bit2)
		{
			return 2;
		}
		else
			return 3;
	}
private:
	BitSet<N> _bs1;
	BitSet<N> _bs2;
};
相关推荐
芒果爱编程2 小时前
MCU、ARM体系结构,单片机基础,单片机操作
开发语言·网络·c++·tcp/ip·算法
明明跟你说过2 小时前
【Go语言】从Google实验室走向全球的编程新星
开发语言·后端·go·go1.19
凌盛羽3 小时前
C#对Excel表csv文件的读写操作
开发语言·windows·物联网·microsoft·c#·excel
VBA63373 小时前
VBA高级应用30例应用在Excel中的ListObject对象:向表中添加注释
开发语言
Dontla3 小时前
Rust字节数组(Byte Array)Rust u8、Vec<u8>、数组切片、向量切片、字符串转字节数组转字符串、&[u8]类型:字节数组引用
开发语言·rust
走在考研路上5 小时前
Python错误处理
开发语言·python
数据小爬虫@5 小时前
Python爬虫:如何优雅地“偷窥”商品详情
开发语言·爬虫·python
CV大法好5 小时前
刘铁猛p3 C# 控制台程序引用System.Windows.Forms报错,无法引用程序集 解决方法
开发语言·c#
工业甲酰苯胺5 小时前
C语言之输入输出
c语言·c++·算法
Days20505 小时前
uniapp小程序增加加载功能
开发语言·前端·javascript