[数据结构]——位图原理及实现

位图

今天我们所介绍的数据结构叫做位图,在谈什么是位图之前我们先来看一道"非常简单的题":有40亿个无符号的整型数据,现在给定一个目标数字,判断这个数字是否在这40亿数据中。题目看起来确实非常简单,有的同学说直接遍历一遍不就ok了吗?还有的同学给出了更高效的查找方式就是将这些数字排序然后进行二分查找。但是,这是有问题的,问题并不在于你搜索这个数字的效率问题,而是你在遍历也好排序也罢,这些数字在内存中放的下么?

一个整型int就是4个字节,10亿个int差不多已经需要4G的内存了,40亿个int就是16G。所以这里方法行不通的根本原因实际上是内存不够,但是我们今天的讲的位图却能很好的帮助我们处理这个问题。

位图模型

既然根本原因是这些数据用int放不下,那么是否有更小的东西标记这些数字呢?没错,有的同学想到了,char只占一个字节或许能表示一个数字,但是随着数字位数的增多,依旧不可能使用一个字符表示一个数字,这就意味着小于4G内存还是不能解决这个问题。
其实说到这里,我们的问题就转化为如何使用更小的内存单元来标记一个数字,而在程序中我们最小的访问单位的bit位,所以现在我们一起来看使用比特位如何标记(映射)这些数据。
[数据结构]——位图原理及实现
现在我们发现,4个字节本来只能存储一个int,而现在使用位图我们就存了(映射)32个数字,意味着16G/32约等于500m左右我们就能映射这些数据,那么这些数据是怎么映射到位图种的呢?接着看。

设计位图

为了方便,我们将位图用一个数组表示,让vector帮我们开辟一段连续的空间,我们只负责将数据设置或者移除就行。

class BitMap
{
public:
	BitMap(size_t range)
	{
		//右移5位相当于除以32,加1是因为小于32的数字如果与32相除则得到0
		_bitTable.resize((range >> 5) + 1);
	}
	
private:
	vector<int> _bitTable;
};

位图元素的设置

	void SetBit(size_t x)
	{
		size_t index = x >> 5;
		size_t num = x % 32;

		_bitTable[index] |= (1 << num);
	}

来看看为什么需要size_t index = x >> 5size_t num = x % 32两步操作:我们看看要映射5和32这俩个数
[数据结构]——位图原理及实现
5表示防在第1个整型空间的第5位上,32则表示放在第2个整型空间第一位上。而**bitTable[index] |= (1 << num)**能保证把第num位上的数字设置为1,其余数字保持不变。

位图元素的移除

比较简单,需要知道的是**~(1 << num)**表示出了num位为0,其余位都为1.

	void RemoveBit(size_t x)
	{
		size_t index = x >> 5;
		size_t num = x % 32;

		_bitTable[index] &= ~(1 << num);
	}

位图元素的查找

这个没啥好说的,很简单,说到这里,你的位图也就实现完了,非常简单把

bool TestBit(size_t x)
	{
		size_t index = x >> 5;
		size_t num = x % 32;

		return _bitTable[index] & (1 << num);
	}

完整代码:

class BitMap
{
public:
	BitMap(size_t range)
	{
		_bitTable.resize((range >> 5) + 1);
	}

	//标识一个数字在位图中的位置
	void SetBit(size_t x)
	{
		size_t index = x >> 5;
		size_t num = x % 32;

		_bitTable[index] |= (1 << num);
	}

	//取消数字在位图当中的标识.
	void RemoveBit(size_t x)
	{
		size_t index = x >> 5;
		size_t num = x % 32;

		_bitTable[index] &= ~(1 << num);
	}


	bool TestBit(size_t x)
	{
		size_t index = x >> 5;
		size_t num = x % 32;

		return _bitTable[index] & (1 << num);
	}

private:
	vector<int> _bitTable;
};

拓展

现在将问题修改为让你寻找出40亿个数据中出现过两次的数据,此时我们就需要使用两位来标记同一个数据了。

N位位图的实现如下:

class NBitMap
{
public:
	NBitMap(size_t range)
	{
		_bitTable.resize((range >> 4) + 1);
	}

	void SetBit(size_t x)
	{
		size_t index = x >> 4;
		size_t num = x % 16;
		num *= 2;

		bool first = _bitTable[index] & (1 << num);
		bool second = _bitTable[index] & (1 << (num + 1));

		if (!(first && second))
		{
			_bitTable[index] += (1 << num);
		}
	}

	bool TestBit(size_t x)
	{
		size_t index = x >> 4;
		size_t num = x % 16;
		num *= 2;

		return (_bitTable[index] >> num) & 0x03;
	}

private:
	vector<int> _bitTable;
};

关于位图的讲解就到这里,现在我让你查找10亿个字符串中出现一次的那个字符串,有的同学丝毫不犹豫就要用我们使用的位图,但是仔细思考,我们这里的位图只是可以映射数字类型的数据,变成字符串以及其他文件好像就不再那么得心应手了,别急,聪明的大佬们又想到了一种骚东西叫做布隆过滤器,那么布隆过滤器是什么呢?请看下篇博客哦。