引入
再说C++位图之前,我们先看一个经典的面试题。
给40亿个不重复的且无序的 unsigned int 整数,然后给出一个整数,判断这个数是否在这40亿个数中?
我们简单分析一下题目:
- 快速判断。在编程中提到效率,一般有一招, “空间换时间”。具体实现就是借助数组的查询时间复杂度为常数级别( O(1) )。
- 判断是否存在。 只有两个状态,存在和不存在,正好对应二进制的0和1。
那我们就可以这样设计了。
用数组的下标索引值表示数字,如果有则对应位设置为 1,否则为 0。
那我们就创建一个40亿大小的数组吧。数组存放什么类型的数据呢?bool?char?int?short?
都不太好,就算只占一个字节的bool类型,也有8bit,而我们只需1bit表示是否存在。即存在为1,不存在为0。
这样存在大量空间的浪费。设想一下,40亿个数据将占用多少内存,又有多少内存是可以节约下来的?至少7/8.
那我们怎么办?
位图
C++中的位图通常指的是使用位运算来表示和处理数据的数据结构。
取位
上面我们说的bool类型有8位,我们可以借助简单的运算来实现一种映射,将每八位数据对应一个bool类型的8位bit。
类似先定位段,再定位在段中的位置。
要存放一个数据 value,要找到它对应bool数组的元素的位置索引,然后在找到它对应元素的多少位。
定位存放索引:value / (8 * sizeof(value_type))
。
定位bit位值: value % (8 * sizeof(value_type))
。
上面如果是bool,则8 * sizeof(value_type)取8;如果是 int ,就是4*8=32位。
设置位
借助位运算
可以很简单的实现。
设置为 1: b[value / 8] |= (1 << (value % 8))
判断是否存在: b[value / 8] & (1 << (value % 8))
很好理解,不过我们还是举个例子。
bool 数组b, 存放 value , 值为 21。
21 / 8 = 2, 故 value 存放在数组的索引为2位上,即 b[2]
21 % 8 = 5, 故 valeu 存放在b[2]上的索引为 5 的位上。
设置存在
bool[2] |= (1<<5);
使用
现在解决最初给出的题目
给40亿个不重复的且无序的 unsigned int 整数,然后给出一个整数,判断这个数是否在这40亿个数中?
代码
#include <iostream>
#include <vector>
using namespace std;
#define SIZET_LEN (sizeof(size_t) * 8)
class BitMap
{
public:
BitMap(size_t _size)
{
this->size = _size;
ibits.resize(_size / SIZET_LEN, 0);
}
void Set(size_t index)
{
if (index <= size)
ibits[index / SIZET_LEN] |= (ONE << (index % SIZET_LEN));
}
bool Exist(size_t index)
{
if(index <= size)
return ibits[index / SIZET_LEN] & (ONE << (index % SIZET_LEN));
return false;
}
void Reset(size_t index)
{
if(index <= size)
ibits[index / SIZET_LEN] &= ~(ONE << (index % SIZET_LEN));
}
protected:
std::vector<size_t> ibits;
size_t size;
static size_t ONE;
};
size_t BitMap::ONE = 1;
void test1()
{
BitMap b(unsigned(-1));
b.Set(2);
b.Set(479);
b.Set(5678765);
b.Set(999999999);
b.Set(0xFFFFFFFF);
cout << b.Exist(478) << endl;
cout << b.Exist(479) << endl;
cout << b.Exist(5678765) << endl;
cout << b.Exist(999999999) << endl;
cout << b.Exist(0xFFFFFFFF) << endl;
cout << "---------\n";
cout << b.Exist(5678765) << endl;
b.Reset(5678765);
cout << b.Exist(5678765) << endl;
}
int main()
{
test1();
return 0;
}