Ⅰ、bitset的介绍
位图:
- 就是用 比特位 来标识某种状态,适用于海量数据,数据无重复的场景。通常是用来判断某个数据存不存在的。
位图的接口:
cpp
成员函数 功能
set 设置指定位或所有位
reset 清空指定位或所有位
flip 反转指定位或所有位
test 获取指定位的状态
count 获取被设置位的个数
size 获取可以容纳的位的个数
any 如果有任何一个位被设置则返回true
none 如果没有位被设置则返回true
all 如果所有位都被设置则返回true
- 注意:库里面的函数可以是指定位,也可以是所有位。
栗子:
给 40 亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在
这 40 亿个数中。
一般思路:
- 排序+二分
- 哈希(unordered_map)
在这里,我们可以用位图解决,空间复杂度会变低。
- 数据是否在给定的整形数据中。结果是在或者不在,刚好是两种状态。
- 那么可以使用一个二进制比特位来代表数据是否存在的信息。
- 如果二进制比特位为1,代表存在,为0代表不存在。
比如:
位图的应用:
- 快速查找某个数据是否在一个集合中。
- 排序+去重。
- 求两个集合的交集、并集等。
- 内核中信号标志位(信号屏蔽字和未决信号集)。
Ⅱ、位图的实现
1 .整体框架
- 我们选择用一个vector来存放数据。具体开多大的空间,可以用模板来表示。
- 在构造函数中,用户模板传入的多大字节。其中我们把用户传的大小除以32再加1。
- 判断多少个整形可以有这么多字节的空间,但是会浪费几个比特位,这些空间都是可以忽略的。
cpp
class BitSet
{
public:
BitSet()
{
_bs.resize(N / 32 + 1);
}
private:
vector<int> _bs;
};
2 .把某一位设置为1
这里有两步:
- 找到x所处在的那一位: 先确定这个数应该处在第几个整数(类似哈希表找存储位置),也就是index=x/32;然后通过把x对32取模,得到x在第index个整形的第pos个位置
- 把改为设置为1: 把1左移pos位,用这个数和第index整数进行按位或的操作
cpp
void set(size_t x)
{
int i = x / 32;
int y = x % 32;
_bs[i] |= (1 << y);
}
3 . 把某一位设置为0
两步:
- 找到那一位: 同上
- 把这位设置为0: 把1左移pos位,把这个数取反,然后和第index个整数进行按位与的操作
cpp
void reset(size_t x)
{
int i = x / 32;
int y = x % 32;
_bs[i] &= (~(1 << y));
}
4 . 判断某一位是否为1
两步:
- 找到那一位: 同上
- 把这位设置为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;
};