1. 问题描述
输入: 一个最多包含n个不重复的正整数的文件,其中每个数都小于n,每个数是一个7位的整数, n=10^7。
条件: 最多有1MB的内存可用, 排序最多只允许执行几分钟,10s是比较理想的运行时间.有充足的磁盘存储空间可用.
输出: 按升序排列的输入整数的列表.
2. 解决方案
2.1 归并排序
由于内存的限制, 只能采用多路归并的方法来解决这个问题.
排序方法; 把这个文件分为若干大小的几块,然后分别对每一块进行排序,最后完成整个过程的排序。k趟算法可以在kn的时间开销内和n/k的空间开销内完成对最多n个小于n的无重复正整数的排序。比如可分为2块(k=2,1趟反正占用的内存只有1.25/2M),1~4999999,和5000000~9999999。先遍历一趟,首先排序处理1~4999999之间的整数(用5000000/8=625000个字的存储空间来排序0~4999999之间的整数),然后再第二趟,对5000001~1000000之间的整数进行排序处理。
排序步骤:
(1) 内存排序: 由于要求的可用内存为1MB,那么每次可以在内存中对250K的数据进行排序,然后将有序的数写入硬盘。那么10M的数据需要循环40次,最终产生40个有序的文件。
(2) 归并排序:
- 将每个文件最开始的数读入(由于有序,所以为该文件最小数),存放在一个大小为40的first_data数组中;
- 选择first_data数组中最小的数min_data,及其对应的文件索引index;
- 将first_data数组中最小的数写入文件result,然后更新数组first_data(根据index读取该文件下一个数代替min_data);
- 判断是否所有数据都读取完毕,否则返回2。
2.2 位图方案
数据表示: 使用一个具有1000万个位的字符串来表示输入文件数据, 其中,当且仅当整数i在文件中存在时,第i位为1.
采用位图原因: 利用了该问题在排序问题中不常见的3个属性:
- 输入数据限制在相对较小的范围内
- 数据没有重复
- 对于每条记录而言, 除单一整数外, 没有任何其他关联数据
位图解决三个步骤:
- 第一步, 将所有位置为0, 从而将集合初始化为空
- 第二步, 读入文件中的每个整数来建立集合, 将每个对应的位置为1
- 第三步, 检验每一位, 若该位为1, 就输出相应的整数, 由此产生有序的输出文件
//第一步,将所有的位都初始化为0
for i ={0,....n}
bit[i]=0;
//第二步,通过读入文件中的每个整数来建立集合,将每个对应的位都置为1。
for each i in the input file
bit[i]=1;
//第三步,检验每一位,如果该位为1,就输出对应的整数。
for i={0...n}
if bit[i]==1
write i on the output file
java实现代码如下:
/*
* 位向量的实现及其应用在1000万个数的排序中
*/
public class Bitvector {
public static void main(String[] args){
int number = 10000000; //待排序数的数量
int len = (number - 1)/32 +1; //java中int占4字节,一位代表一个数,分配多少个int型变量
int[] a = new int[len];
int[] data = new int[number];
//产生随机输入数据,数据是乱序的
int temp,temp1;
Random rand = new Random();
for(int i=0;i<10000000;i++){
data[i] = i;
//随机交换
temp = rand.nextInt(i+1);
temp1 = data[temp];
data[temp] = data[i];
data[i] = temp1;
}
for(int i=0;i<len;i++){
a[i]=0;
}
for(int i=0;i<number/10;i++){
set(a,data[i]);
}
for(int i=0;i<number/10;i++){
if(read(a,i)==1)
System.out.print(i+" ");
}
}
//将第i位置为1
public static void set(int[] num,int i){
num[i>>5] |= (1<<(i&0x1F)); //i>>5指的是i/32,确定其在哪一个int中,i&0x1F则是i%32,确定其在int中的哪一位
}
//将第i位置为0
public static void clr(int[] num, int i){
num[i>>5] &= ~(1<<(i&0x1F));
}
//读取第i位,判断是0/1
public static int read(int[] num,int i){
return num[i>>5] & (1<<(i&0x1F));
}
}
上述java实现的是对100万个数据进行排序, 这些数据的大小在1000万之间, 由于限制了内存只有1M, 而1000 0000/(8*1024*1024)>1M, 故实现时应该分2趟进行排序,即
- 第一次,只处理1—4999999之间的数据,这些数都是小于5000000的,对这些数进行位图排序,只需要约5000000/8=625000Byte,也就是0.625M,排序后输出。
- 第二次,扫描输入文件时,只处理4999999-10000000的数据项,也只需要0.625M(可以使用第一次处理申请的内存)。
因此,总共也只需要0.625M