位图(Bit Map)是一个数组的每一个数据的每一个二进制位表示一个数据,0表示数据不存在,1表示数据存在。
不好理解的话,由下面这道题来引出位图的使用:
给出40亿个不重复的无符号整数,没排过序,给出一个无符号整数,如何快速判断一个数是否在这40亿个数中?
解题思路:
- 首先没由给出数的范围,只说了无符号整数,所以就要考虑到0到最大整数42亿的整个区间的所有数。
- 所有数据没有排过序,故而不能使用类似二分查找这样快速的方法去判断在不在。
- 开一个可以容纳所有整数的数组,42亿个字节差不多就是4个G的内存,一个整型占4个字节,一共就要16G的内存来存放这些数,16G内存,作为面试题,这个思路字节PASS掉吧。
- 既然一个整型代表一个数太过浪费,那就可以考虑用一个bite来表示一个数,一个整型4个字节,一个字节8个位,这样下来,一个整型就可以代表32个数,这样算下来,42亿只需要500M的空间,想想好像可以解决了。
基于这样的思想,看如下图示:
将每个存在的数对应的比特位都置为1,则可以在O(1)时间复杂度内判断一个数存在还是不存在。
上面就是位图的具体应用,即判断在还是不在,在最大局限就是只能对整数进行操作,如果是判断字符串呢?后续会讲到其他数据结构。
那么基于以上思想写出位图代码:
class BitMap
{
public:
BitMap(size_t range)
{
//一共开多少个字节,余数的原因,所以+1
_bits.resize(range >> 3 + 1,0);
}
//将一个数添加进位图
void Set(size_t value)
{
//计算出位于哪个字节
size_t index = (value>>3);//尽量以移位操作代替乘除操作
//计算出具体在该字节哪个位
size_t num = value % 8;
//将该位置为1
_bits[index] |= (1 << num);
}
//将一个数从位图中去除
void ReSet(size_t value)
{
size_t index = (value >> 3);
size_t num = value % 8;
_bits[index] &= (~(1 << num));
}
//判断一个数是否在位图中存在
bool Test(size_t value)
{
size_t index = (value >> 3);
size_t num = value % 8;
return _bits[index] &(1 << num);
}
protected:
vector<char> _bits;
};
位图的扩展
如果将上面那道题目改成,在40亿无符号整数中找出只出现一次的数。
如果是这样的话,那么一位是0或者1,只能表示两种状态,已经解决不了目前这种情况了,因为判断一个是否只出现一次对应三种状态:出现0次,出现一次,出现多次。
两个位有00 01 10 11四种状态,可以用00表示改数不存在,01表示只出现一次,用10或者11表示多次就可以了。
位图的应用:
- 可以对整数进行排序,对应位置位1,最后遍历整个位图就可以将一组整型排序。
- linux下信号的block表,pending表。
- 操作系统中,对pid号的管理使用位图,这样操作系统就可以快速判断哪个pid尚未分配出去,从而分配给新的进程一个pid号。
- linux下文件权限的保存
总之只要是判断在或者不在的情况,都可以使用位图来解决,但是位图的局限性就在于只能针对整型,对于字符串位图无法解决。
如果想要判断一个字符串是否在一大堆字符串是否存在,我们可以采用另一种数据结构,即布隆过滤器!