<<编程珠玑>>开篇就讲了一个用位图散列解决用少量内存解决大量数据排序的问题.
现在有10 000 000条互不相同的数据,2M的内在,无限大的硬盘空间.用散列表来实现这些数据的存储和查找.
假如要存储一组小于20的非负整数的集合,可以用一个20bit的串来表示.比如集合{1,5,8,13}可以表示为:
0 1 0 0 0 1 0 0 1 0 0 0 0 1 0 0 0 0 0 0
第i位上有数据则置为1,否则置为0
因此存储10 000 000条数据需要10 000 000bit=1 250 000Byte=1.25M<2M
构建散列表的同时就完成了对数据和排序,如果要查找时间复杂度也只是O(1)
使用位图存储集合的情况并不多见,因为它有3个不常见的使用条件:输入数据限制在范围相对较小的范围内;数据没有重复;每条记录除单一整数外没有任何其他关联数据.
因此散列表是一种以空间换取时间的技术.
位图散列的一道练习题:有个无序的正整数数列(存放在数据库中),容量为2^30,其元素取值范围为[1,2^30],且元素各不相同。现从中删除了若干个(小于10)元素,请找出一个被删除的元素。要求:内存使用不要超过1M,可以多遍扫描数据库。
如果直接使用位图散列至少需要2^30(1G)的内存。第一遍扫描数据库可以先确定缺失的元素出现在哪个范围内(这个范围包含的数据量不能超过1M);第2遍扫描数据库把出现在这个范围内的元素都映射到一个位图,从而找出缺失的元素。
把2^30平均分为2^15段,每段的容量是2^15。(2^15小于1M)。第一遍扫描数据库记录每段中出现的元素个数,如果少于2^15说明本段有缺失元素。
下面给一个示例的程序:
#ifndef BIT_MAP_H
#define BIT_MAP_H
#include<vector>
using std::vector;
class BitMap{
private:
int scope; //map [1,scope] to bit array
char *map_array; //bit array
int arrlen; //array length
public:
BitMap(int len); //assign the scope
void mapToBit(int number); //map a integer to a bit
vector<int> lackNumber() const; //find which numbers are missed
};
BitMap::BitMap(int maxnum):scope(maxnum){
if(scope & 7U){ //can't be divisible by 8
arrlen=(scope>>3) + 1; /* scope/8+1 */
}
else{
arrlen=scope>>3; /* scope/8 */
}
map_array=new char[arrlen]; //initialize the bit array
}
void BitMap::mapToBit(int number){
if(number<=0 || number>scope) //number is out of scope
throw "The number is out of scope";
int index=number>>3; /* number/8 */
int remainder=number & 7U; //the remainder after divided by 8
map_array[index] |= (1U<<(remainder-1)); //set the bit(respond to the int number) to 1
}
vector<int> BitMap::lackNumber() const{
vector<int> rect;
for(int i=0;i<arrlen;++i){
char ch=map_array[i];
for(int j=0;j<8;++j){
if( (ch&1U) == 0){ //the tail bit is zero
rect.push_back(8*i+j+1); //the responding integer is lacked
}
ch >>= 1; //rotate right by 1,check if the left bit is zero
}
}
/* erase the integer which is more than scope */
vector<int>::iterator iter=rect.end()-1;
while(iter!=rect.begin()){
if(*iter > scope){
vector<int>::iterator tmp=iter;
iter--;
rect.erase(tmp);
}
else
break;
}
if(iter==rect.begin() && *iter>scope){
rect.erase(iter);
}
return rect;
}
#endif
#include"BitMap.h"
#include<cstdlib>
#include<algorithm>
#include<iostream>
using namespace std;
int main(){
vector<int> vec(100);
for(int i=0;i<vec.size();++i){
vec.at(i)=i+1;
}
vec.erase(vec.begin()+12); //delete 13
vec.erase(vec.begin()+62); //delete 64
vec.erase(vec.begin()+66); //delete 69
random_shuffle(vec.begin(),vec.end());
int *count=new int[10]();
for(int i=0;i<vec.size();++i){
count[(vec.at(i)-1)/10]++;
}
BitMap bitmap(100/10);
int i;
for(i=0;i<10;++i){
if(count[i]<10){
int base=10*i;
for(int j=0;j<vec.size();++j)
if((vec.at(j)-1)/10==i)
bitmap.mapToBit(vec.at(j)-base);
break;
}
}
delete count;
vector<int> lacks=bitmap.lackNumber();
cout<<"The first lack number is "<<lacks[0]+10*i<<endl;
return 0;
}
下面讲散列的一个应用,不一定是用位图散列了。
下面讲中文分词(Chinese Word Segmentation)中的搜索技术.
要解决的问题是确定一个汉字串是否为一个词.关键技术是把把汉语词典中的词都放到一个散列表中.
我们以每个词的第一个汉字为其关键码.
GB2312-80一级字库内有3755个编码,其编码有一定规则:
- 每个汉字两个字节
- 第一个字节(高字节)的值大于176
- 第二个字节(低字节)的值大于等于161而小于255
设计如下散列函数:
H(汉字编码)=(汉字编码高字节-176)*94+(汉字编码低字节-161)
这样3755个映射到散列表中的地址刚好是唯一的,不多也不少.这里"-176","-161"好理解,那"*94"是为什么呢?255-161=94!低字节满94后就要向高字节进位,就好比十进制23=2*10+3一样.