【STL】bitset(位图) 的介绍,使用,模拟实现

bitset的介绍

引入位图

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

要判断一个数是否在某一堆数中,我们可能会想到如下方法:

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

单从方法上来看,这两种方法都是可以,而且效率也不错,第一种方法的时间复杂度是O (NlogN) ,第二种方法的时间复杂度是O(N)。

但问题是这里有40亿个数,若是我们要将这些数全部加载到内存当中,那么将会占用16G的空间,空间消耗是很大的。因此从空间消耗来看,上面这两种方法实际都是不可行的。

位图解决

实际在这个问题当中,我们只需要判断一个数在或是不在,即只有两种状态,那么我们可以用一个比特位来表示数据是否存在,如果比特位为1则表示存在,比特位为0则表示不存在。比如:

无符号整数总共有2^32个,因此记录这些数字就需要2^32个比特位,也就是512M的内存空间,内存消耗大大减少。

位图的概念

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

位图的应用

常见位图的应用如下:

  1. 快速查找某个数据是否在一个集合中。
  2. 排序。
  3. 求两个集合的交集、并集等。
  4. 操作系统中磁盘块标记。
  5. 内核中信号标志位(信号屏蔽字和未决信号集)。

bitset的使用

bitset的定义方式

方式一: 构造一个16位的位图,所有位都初始化为0。

bitset<16> bs1; //0000000000000000

方式二: 构造一个16位的位图,根据所给值初始化位图的前n位。

bitset<16> bs2(0xfa5); //0000111110100101

方式三: 构造一个16位的位图,根据字符串中的0/1序列初始化位图的前n位。

bitset<16> bs3(string("10111001")); //0000000010111001

bitset成员函数的使用

bitset中常用的成员函数如下:

set:设置指定位或所有位
reset:清空指定位或所有位
flip:反转指定位或所有位
test:获取指定位的状态
count:获取被设置位的个数
size:获取可以容纳的位的个数
any:如果有任何一个位被设置则返回true
none:如果没有位被设置则返回true
all:如果所有位都被设置则返回true

bitset运算符的使用

一、bitset中>>、<<运算符的使用。

bitset容器对>>、<<运算符进行了重载,我们可以直接使用>>、<<运算符对biset容器定义出来的对象进行输入输出操作。

int main()
{
	bitset<10> bs;

	cin >> bs; //100101

	cout << bs << endl; //0000100101

	return 0;
}

二、bitset中赋值运算符、关系运算符、复合赋值运算符、单目运算符的使用。

 bitset容器中不仅对赋值运算符和一些关系运算符进行了重载,而且对一些复合赋值运算符和单目运算符也进行了重载,我们可以直接使用这些运算符对各个位图进行操作。

包括如下运算符:

  • 赋值运算符:=。
  • 关系运算符:==、!=。
  • 复合赋值运算符:&=、|=、^=、<<=、>>=。
  • 单目运算符:~。
int main()
{
	bitset<10> bs1(string("1010101"));
	bitset<10> bs2(string("0101010"));

	bs1 |= bs2;
	cout << bs1 << '\n'; //1110111101

	bs1 &= bs2;
	cout << bs1 << '\n'; //1000001000

	return 0;
}

三、bitset中位运算符的使用。

bitset容器中同时也对三个位运算符进行了重载,我们可以直接使用&、|、^对各个位图进行操作。

int main()
{
	bitset<10> bs1(string("1010101"));
	bitset<10> bs2(string("0101010"));

	cout << (bs1|bs2) << '\n'; //1110111101

	cout << (bs1&bs2) << '\n'; //0000000000

	return 0;
}

四、bitset中[ ]运算符的使用。

bitset容器中对[ ]运算符进行了重载,我们可以直接使用[ ]对指定位进行访问或修改

int main()
{
	bitset<10> bs2(0xaa);

	cout << bs2 << endl; //0010101010

	bs2[1] = 0;
	bs2[3] = 0;
	bs2[5] = 0;
	bs2[7] = 0;

	cout << bs2 << endl; //0000000000

	return 0;
}

bitset的模拟实现

namespace cl
{
	//模拟实现位图
	template<size_t N>
	class bitset
	{
	public:
		//构造函数
		bitset();
		//设置位
		void set(size_t pos);
		//清空位
		void reset(size_t pos);
		//反转位
		void flip(size_t pos);
		//获取位的状态
		bool test(size_t pos);
		//获取可以容纳的位的个数
		size_t size();
		//获取被设置位的个数
		size_t count();
		//判断位图中是否有位被设置
		bool any();
		//判断位图中是否全部位都没有被设置
		bool none();
		//判断位图中是否全部位都被设置
		bool all();
		//打印函数
		void Print();
	private:
		vector<int> _bits; //位图
	};
}
#pragma once

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

namespace BS
{
	template <size_t N>
	class BitSet
	{
	public:
		BitSet()
		{
			_bits.resize(N / 32 + 1);
		}

		void set(size_t pos)
		{
			assert(pos < N);
			//哪一个区间
			int i = pos / 32;
			//区间的哪一个位置
			int j = pos % 32;

			_bits[i] |= (1 << j);

			//Set = false;
		}

		void reset(size_t pos)
		{
			assert(pos < N);
			//哪一个区间
			int i = pos / 32;
			//区间的哪一个位置
			int j = pos % 32;

			_bits[i] &= ~(1 << j);
		}

		void flip(size_t pos)
		{
			assert(pos < N);
			//哪一个区间
			int i = pos / 32;
			//区间的哪一个位置
			int j = pos % 32;

			_bits[i] ^= (1 << j);
		}

		bool test(size_t pos)
		{
			assert(pos < N);
			//哪一个区间
			int i = pos / 32;
			//区间的哪一个位置
			int j = pos % 32;

			return (_bits[i] >> j) & 1;
		}

		size_t size()
		{
			return N;
		}

		size_t count()
		{
			size_t cnt = 0;
			for (int i = 0; i < N; i++)
			{
				if(test(i))
				{
					cnt++;
				}
			}

			return cnt;
		}

		bool any()
		{
			for (const auto& a: _bits)
			{
				if (a != 0)  //被设置过的元素值不可能为0
					return false;
			}

			return true;
		}

		bool nonu()
		{
			return !any;
		}

		bool all()
		{
			int n = _bits.size();
			//先检查前n - 1个元素
			for (int i = 0; i < n - 1; i++)
			{
				if ((~_bits[i]) != 0)
				{
					return false;
				}
			}

			//然后检查最后一个元素的前N位
			for (int i = 0; i < N % 32; i++)
			{
				if(!(_bits[n - 1] & (1 << i)))
				//if (!test(i))
					return false;
			}

			return true;
		}

		void Print()
		{
			size_t n = _bits.size();
			for (int i = 0; i < N; i++)
			{
				if (test(i))
				{
					printf("1");
				}
				else
				{
					printf("0");
				}
			}
		}

	private:
		//int a[N];
		vector<int> _bits;
		//位图是否被设置,没有为true
		//bool Set = true;
	};

}
### C++STL Bitmap 的使用及相关信息 在标准模板库(STL)中,并不存在名为 `STLBitmap` 的特定容器或类。然而,在C++编程实践中,位图(bitmap)通常用于表示集合或标志数组。为了实现类似的功能,程序员经常使用 `std::vector<bool>` 或者自定义结构来模拟 bitmap 行为。 #### 使用 std::vector<bool> 实现 BitMap 功能 虽然 `std::vector<bool>` 并不是严格意义上的 bit-level 容器,但它被专门优化用来存储布尔值序列并尽可能紧凑地压缩内存占用[^1]: ```cpp #include <iostream> #include <vector> int main() { // 创建一个大小为 100, 初始值全部设为 false 的 vector<bool> std::vector<bool> bitmap(100); // 设置某些位置上的比特位 bitmap[10] = true; bitmap[20] = true; // 遍历打印当前设置的状态 for (size_t i = 0; i < bitmap.size(); ++i){ if(bitmap[i]){ std::cout << "Bit at position " << i << " is set.\n"; } } return 0; } ``` 需要注意的是,尽管 `std::vector<bool>` 提供了一种简单的方式来处理位集,但由于其实现细节可能与其他类型的向量不同,因此有时会带来意想不到的行为或性能问题。对于更复杂的应用场景,建议考虑其他第三方库提供的专用bitmap数据结构,比如 Boost.Dynamic_Bitset[]。 #### 自定义 BitMap 类 如果希望获得更好的控制力以及更高的效率,也可以创建自己的 bitmap 类: ```cpp class SimpleBitMap { private: unsigned int* bits_; size_t num_bits_; public: explicit SimpleBitMap(size_t nbits):num_bits_(nbits), bits_((unsigned int*)calloc(((nbits + 31)/32), sizeof(unsigned int))) {} ~SimpleBitMap(){ free(bits_); } void SetBit(size_t pos); bool GetBit(size_t pos)const ; }; void SimpleBitMap::SetBit(size_t pos){ if(pos >= this->num_bits_) throw std::out_of_range("Position out of range"); bits_[pos / 32] |= (1u << (pos % 32)); } bool SimpleBitMap::GetBit(size_t pos)const{ if(pos >= this->num_bits_) throw std::out_of_range("Position out of range"); return ((this->bits_[pos / 32]) >> (pos % 32)) & 1u; } ``` 上述代码片段展示了如何构建一个简单的 bitmap 类型,其中包含了基本的操作方法如设定某一位(`SetBit`) 和获取某一位状态 (`GetBit`). 这样的设计允许更加灵活地管理大量二进制标记而不会浪费过多空间.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值