如何给1000万条记录排序,每条记录都是7位的整数

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) 归并排序:

  1. 将每个文件最开始的数读入(由于有序,所以为该文件最小数),存放在一个大小为40的first_data数组中;
  2. 选择first_data数组中最小的数min_data,及其对应的文件索引index;
  3. 将first_data数组中最小的数写入文件result,然后更新数组first_data(根据index读取该文件下一个数代替min_data);
  4. 判断是否所有数据都读取完毕,否则返回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
***位图的适用范围仅限于针对不重复的数据进行排序.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值