bitset的介绍
引入位图
给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在这40亿个数中?
要判断一个数是否在某一堆数中,我们可能会想到如下方法:
- 将这一堆数进行排序,然后通过二分查找的方法判断该数是否在这一堆数中。
- 将这一堆数插入到unordered_set容器中,然后调用find函数判断该数是否在这一堆数中。
单从方法上来看,这两种方法都是可以,而且效率也不错,第一种方法的时间复杂度是O (NlogN) ,第二种方法的时间复杂度是O(N)。
但问题是这里有40亿个数,若是我们要将这些数全部加载到内存当中,那么将会占用16G的空间,空间消耗是很大的。因此从空间消耗来看,上面这两种方法实际都是不可行的。
位图解决
实际在这个问题当中,我们只需要判断一个数在或是不在,即只有两种状态,那么我们可以用一个比特位来表示数据是否存在,如果比特位为1则表示存在,比特位为0则表示不存在。比如:
无符号整数总共有2^32个,因此记录这些数字就需要2^32个比特位,也就是512M的内存空间,内存消耗大大减少。
位图的概念
所谓位图,就是用每一位来存放某种状态,适用于海量数据,数据无重复的场景。通常是用来判断某个数据存不存在的。
位图的应用
常见位图的应用如下:
- 快速查找某个数据是否在一个集合中。
- 排序。
- 求两个集合的交集、并集等。
- 操作系统中磁盘块标记。
- 内核中信号标志位(信号屏蔽字和未决信号集)。
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;
};
}