所谓的bit-map其实很直观,就是使用一个位来表示元素是否存在。经常用在索引以及数据压缩的方面。下面讲一下使用位图的方式对数组进行排序。
例如:对于数组 { 3, 5 , 4 , 9, 8 } ,需要的位数至少为9+1=10位(第0位表示数字0),即两个字节。因此位图的示意图为:
从第一个字节的第一位算起(我自己的程序是这样定义的),为1即表示改下标数字在数组中存在。
例如在《编程珠玑》中出现的题目:要求对磁盘上的数据进行排序。
由于内存的限制,我们必须在有限的内存中完成排序,将所有的数据直接加载到内存显然是不现实的,很直观的,我们想到的是将数据分为几次分别加入;这就涉及到磁盘与内存之间额数据交换;但又由于时间的限制,我们必须将减少磁盘与内存之间的数据交换。
观察数据的特征,我们可以知道不存在重复的数据,也就是说对于整个数组而言,只有数据存在或者不存在两种状态,因此只需要用一个二进制位来表示这样的状态即可。
Version 1: 对于上述的要求(无两个相同的元素存在),我们可以有这样的位图排序函数:
<span style="font-size:14px;">const int LENBASE = 10; const int NUMBASE = 20; const int BYTESIZE = 8; void bitMapSort(int num[], int len) { int bufferSize = *(max_element(num, num+len))/BYTESIZE + 1; //用来计算当前的数组需要的最大的位数,即字节的个数。 char *bufferPtr = new char[bufferSize]; memset(bufferPtr,0,bufferSize); for(int i = 0 ; i < len; ++i) { int cur = num[i]; char *temp = bufferPtr + cur/BYTESIZE; *temp = (*temp)|(0x80 >> (cur%BYTESIZE)); } int index = 0 ; for(int i = 0 ; i < bufferSize ; ++i) { unsigned char shift = 0x80; int shiftCount = 0; //<span style="font-family: Arial, Helvetica, sans-serif;">用于表示记录在当前的字节内移动的位数。</span> while(shift) { if( (bufferPtr[i]&shift)^shift == 0 ) { cout << i*BYTESIZE+shiftCount << " "; num[index++] = i*BYTESIZE+shiftCount; } shiftCount++; shift = shift >> 1; } } }</span>
Version 2:
然而,上述程序对数组中没有重复的元素是适用的,但是对于一个元素出现几次的情况:下面的版本是使用一个 unsigned int 类型来记录每个元素的出现次数,代码如下:
<span style="font-size:14px;">void bitMapSort2(int num[], int len) { int bufferSize = *(max_element(num, num+len)); unsigned int *bufferPtr = new unsigned int[bufferSize+1]; //用来记录元素的存在个数 memset(bufferPtr,0,bufferSize); for(int i = 0 ; i < len; ++i) { int cur = num[i]; bufferPtr[cur] += 1; } int index = 0; for(int i = 0 ; i < bufferSize+1 ; ++i) { while(bufferPtr[i]--) num[index++] = i; } cout << endl; }</span>上述版本是实现的比较基础且是针对于位图排序的版本,在C++的STL库中已经实现了bitset:http://www.cplusplus.com/reference/bitset/bitset/?kw=bitset 并且有很强大的功能。
显然,bitset在数据查找也是很有效的,只需要将数据hash到一个很长的二进制矢量中,判断以该数字为下标的位是否为1即可。
在实际应用中,bitmap在索引方面用的比较多,以及数据压缩方面,比如实际的内存不足以容纳要处理的数据时,可以根据数据的特点选择使用数据压缩是否可以时间空间都达到最优。
补充一点,对于数据压缩,bitmap的方法其实就是hash的思想,key是元素自身,value是二值的(1或者0)。顺便提一下,bloom filter,[ 以下内容摘自维基百科]:
如果想判断一个元素是不是在一个集合里,一般想到的是将所有元素保存起来,然后通过比较确定。链表、树、散列表(又叫哈希表,Hash table)等等数据结构都是这种思路。但是随着集合中元素的增加,我们需要的存储空间越来越大。同时检索速度也越来越慢,上述三种结构的检索时间复杂度分别为。
布隆过滤器的原理是,当一个元素被加入集合时,通过K个散列函数将这个元素映射成一个位数组中的K个点,把它们置为1。检索时,我们只要看看这些点是不是都是1就(大约)知道集合中有没有它了:如果这些点有任何一个0,则被检元素一定不在;如果都是1,则被检元素很可能在。也就是说 会把不属于的判断为属于,但是绝对不会把属于的判断为不属于。这就是布隆过滤器的基本思想。