位图
今天介绍的数据结构叫做位图,在介绍之前,首先看一道“简单”的题目,
给出40亿个数据,给出一个四位数字,判断该数字是否在这四十亿个数字中。
这道题难点并不在思路,之前我们使用的链表红黑树等算法思想都能解决这个问题,但是有一点无法解决,40亿个数太太太太大了,换算成int类型大约16G大小的空间,计算机的内存可不一定够,聪明的读者可能想到了char类型来出错结构,从16G的需求降低到4G的需求,但是仍然不够,继续拆分,1个byte可以拆成8bit,假如我们用每一个bit储存数字的存在状态,只需要500M的内存就够用了。
位图就是基于这种思想实现的。
位图模型
下面是比特位映射数字的模型
1个int可以拆成32个bit,假如我们使用32个bit来映射数据,节省了32倍的空间,具体的实现,请往下看
设计位图
为了方便,我们使用vector来替我们管理空间,我们只要做到数字的插入和删除就足够了
位图的构造函数
class BitMap
{
public:
BitMap(const int& size = 0)
{
_bit.resize((size >> 5) + 1);
/*将传入的数字除32,+1是因为假如不是整除,要给余数留一个位置*/
}
private:
vector<int> _bit;
};
位图的插入
void insert(const int& size)
{
int _bitsize = size % 32;//第几个比特位
int index = size / 32;
_bit[index] |= (1 << _bitsize);
}
这里说明一下,int _bitsize = size % 32和int index = size / 32的功能,假设我们要插入两个数字5和32,第一步找到对应字节所在的int,第二步在int中找到,bit位的位置。
**_bit[index] |= (1 << _bitsize)**用位运算将对应bit置为1.
位图的删除
和位图的插入思想相同
void erase(const int& size)
{
int _bitsize = size % 32;
int index = size / 32;
_bit[index] &= ~(1 << _bitsize);
}
**_bit[index] &= ~(1 << _bitsize)**将对应位置为0.
位图的查找
bool findbit(const int& size)
{
int _bitsize = size % 32;
int index = size / 32;
return _bit[index] & (1 << _bitsize);
}
**_bit[index] & (1 << _bitsize)**判断对应bit是否为1.
位图的完整代码
完成了上面的函数的实现,已经将位图实现的七七八八了,下面是位图的完整代码可供参考。
namespace bit
{
class BitMap
{
public:
BitMap(const int& size = 0)
{
_bit.resize((size >> 5) + 1);
}
void insert(const int& size)
{
int _bitsize = size % 32;//第几个比特位
int index = size / 32;
_bit[index] |= (1 << _bitsize);
}
void erase(const int& size)
{
int _bitsize = size % 32;
int index = size / 32;
_bit[index] &= ~(1 << _bitsize);
}
bool findbit(const int& size)
{
int _bitsize = size % 32;
int index = size / 32;
return _bit[index] & (1 << _bitsize);
}
private:
vector<int> _bit;
};
}
拓展
现在将问题修改为查找该数字在40亿数据中是否出现两次,需要将位图进行修改,用2个比特位来储存数字的出现情况。
N位位图的实现如下
class BitMap
{
public:
BitMap(const int& size = 0)
{
_bit.resize((size >> 4) + 1);
}
void insert(const int& size)
{
int _bitsize = size % 16;//计算第几组比特位
int index = size / 16;
bool first = _bit[index] &(1<<_bitsize*2);
bool second = _bit[index] & (1 << _bitsize * 2+1);
if (!(first && second))
{
_bit[index] += (1 << _bitsize * 2);
}
}
void erase(const int& size)
{
int _bitsize = size % 16;
int index = size / 16;
bool first = _bit[index] & (1 << _bitsize * 2);
bool second = _bit[index] & (1 << _bitsize * 2 + 1);
if (!(first && second))
{
_bit[index] -= (1 << _bitsize * 2);
}
}
bool find(const int& size)
{
int _bitsize = size % 16;
int index = size / 16;
return _bit[index] & (1 << _bitsize*2+1);
}
private:
vector<int> _bit;
};
总结
位图的思想和哈希表的思想类似,但是比哈希表实现简单
优点
- 实现简单
- 缺点
- 可读性差,
- 位图对有符号类型数据的存储,需要 2 位来表示一个有符号元素。这会让位图能存储的元素个数,元素值大小上限减半。 比如 8K 字节内存空间存储 short 类型数据只能存 8K*4=32K 个,元素值大小范围为 -32K~32K 。
我们实现的位图只能实现数字的映射,对于string字符串等数据结构的映射就显得无从下手了,不过已经有大佬提前考虑到了这种情况,用一种数据结构解决了这个问题,这个数据结构叫做布隆过滤器,具体的情况请看下一篇博客