源于编程珠玑-第二版
问题描述:(第一章1.2节)
输入:一个最多包含n个正整数的文件,每个数都小于n, n=10^7,
输入文件中整数不重复,且无其他数据与该整数相关联。
输出:按升序排列的整数的列表。
约束:最多有大约1MB的内存空间可用,有充足的磁盘存储空间,运行时间最多几分钟
程序设计:
若每个数都使用32位整数来表示,1MB存储空间约可存250000个整数(10^3 X10^3 /32)可遍历输入文件40次来完成排序。在第1趟中读入0~249999间的整数进内存,完成排序后写入到输出文件。
即第i趟读入(i-1)X250000~(iX250000-1)进行排序,输出到文件。
(每趟在内存中进行排序可选择快速排序,不需要额外辅助空间)。
若采用归并排序,则无需对输入文件遍历40次,即不需要选择(i-1)X250000~(iX250000-1)范围内的数进行排序。可以直接读取然后进行排序,输出到多个临时存储文件中,然后通过多次对临时存储文件的整合,合并成一个输出文件。
采用位向量表示集合:
例如使用20位长的字符串来表示一个所有元素都小于20的简单非负整数集合[0,20)。
{1,2,3,5,8,13}
01110100100001000000
本问题中,输入数据限制在相对较小范围内;数据无重复;且无其他关联数据。
可采用1000W个位字符串(10^7)表示输入文件中的数据,当且仅当整数i存在时,第i位字符为1
伪代码如下:
for i = [0,n)
bit[i]=0
for each i in the input file
bit[i]=1
for i=[0,n)
if bit [i]==1
write i to the output file
---------------------------------------------------------------------
使用int来完成bit位表示的存储:
int n=10000000;
int[] a= new int[n/32+1];
即将长度n分段开,每段的长度是int的大小(32位)
对于整数 i , i/32 则确定该整数应该所属的int段号
确定下int段号后,可通过 i %32 确定在本int中的哪一位
例如 i =75,则 i/32 =2 , i%32 =11 (和二进制11111相与) 即a[2]的第11号位(二进制从右往左) 标为1
以位运算表示为 a[i>>5] |= ( 1<<(i&31) )
int shift = 5;
int mask= 31;
void SetNum(int i){
a[i>>shift] |= ( 1<<(i&mask) ) ;
}
void output( ){
for p =[0,a.length)
for q=[0,32)
if (a[p] & (1<<q) )==(1<<q)
write p*32+q to output file
}
//将每个int分段位进行判断和输出
(题中的存储空间为约1MB,上述位向量存储约使用1.25MB )
public void test1(){
int n=1000000;
int a[]=new int[(n>>5)+1];
int temp;
long time1=System.currentTimeMillis();
for(int i=0;i<n;i++){
a[i>>5]|=(1<<(i&31));
}
for(int p=0;p<a.length;p++){
for(int q=0;q<32;q++){
temp=1<<q;
if((a[p]&temp)==temp)
System.out.print((p<<5)+q+",");
}
}
System.out.println();
System.out.println(System.currentTimeMillis()-time1);
}
/* 测100W 输出耗时66秒.....*/