位图排序

应用位图的排序

引言

生活中我们不可避免的会遇到大数据排序问题,这里是对《编程珠玑》中第1章中所描述的一类排序需求的简单手记。

背景

类似对存储7位电话号码的数据库进行整理,即对不超过一千万个不重复数据进行排序,限制内存占用1M和时间占用小于10s。

预备

时间

对于一趟循环即时间复杂度为O(n)的程序需要至少一千万此操作,嵌套一层循环则为O(n^2),即一千万的平方次,显然是不现实的。所以最佳方案是一趟排序即完成。

内存

在磁盘中排序显然没有全部读入内存中快速,所以要考虑的是如何在1M内存入最多的数据。Jon Bentley给出的思路令我非常惊奇,利用位图存储数据,即用一位数来表示一个7位数据。

位图表示数据

用一个20位长的字符串来表示一个所有元素都小于20的非负整数集,例如可用如下字符串来表示集合{1,2,3,5,8,13}

0 1 1 1 0 1 0 0 1 0 0 0 0 1 0 0 0 0 0 0

代表集合中数据的位置置1,其余位置置0。

思路

用一个10000000位数组对从0000000~9999999的不超过一千万个数进行按位存储,顺序输出即为排序后的数据。
伪代码

define m 10000000
//第一步,置零
for i = [0,m]
    bit[i] = 0

//第二步,置数
for each i in the input file
    bit[i] = 1

//第三步,取数
for i = [0,m]
    if (bit[i] == 1)
        print i

实践

随机生成一千万个数

Random random = new Random();
for (int i = 0; i < bit; i++) {
	arr[i] = -1;
	arr[i] = random.nextInt(bit-1);
	mSort[i] = false;
}

排序

for(int i = 0; i< bit; i++){
	if(arr[i] != -1)
	mSort[arr[i]] = true;
}

输出

for(int i=0 ; i<99;i++){
	if(mSort[i])
		System.out.println(i);
}

完整实例

package bitmapsort;

import java.util.Random;

public class BitMap {
	final int bit = 10000000;
	private int[] arr = new int[bit];
	private boolean[] mSort = new boolean[bit];
	

	public BitMap() {
		// TODO Auto-generated constructor stub
		Random random = new Random();
		for (int i = 0; i < bit; i++) {
			arr[i] = -1;
			arr[i] = random.nextInt(bit-1);
			mSort[i] = false;
		}
	}

	public void sort() {
		for(int i = 0; i< bit; i++){
			if(arr[i] != -1)
			mSort[arr[i]] = true;
		}
	}
	
	
	public void print(){
		for(int i = 0 ; i< bit ;i++){
			if(mSort[i])
				System.out.println(i);
		}
	}
	
	public static void main(String[] args) {
		long time ;
		time = System.currentTimeMillis();
		BitMap BM = new BitMap();
		time = System.currentTimeMillis() - time;
		System.out.println("create time :"+time+"ms");
		
		
		//位图排序
		time = System.currentTimeMillis();
		BM.sort();
		time = System.currentTimeMillis() - time;
		System.out.println("bit sort time :"+time+"ms");
		
		
//		BM.print();
	}
}

运行

第一次运行

第二次运行

第三次运行

第四次运行

此时可看到,使用该算法排序不超过一千万个7位整数仅需不到80ms左右,非常惊艳。

分析

此算法的排序速度虽然很快,但是也有一个弊端,就是这一千万个数中不能出现相同数据,不然会出现数据丢失。适用范围有限。
那么如果改进一下呢?

改进

version 1.1

假设同一个数会存在至多128个,则可以使用byte[]。
代码修改略
实际测试中时间依然为70ms左右。

version 1.2

既然时间充足,如果取消内存限制的话,可以尝试更换为int[],java中int的范围为“-232”到“232-1”,即“-2147483648”到“2147483647”。
此时足够处理一千个数都为同一个数的极限情况。
修改后的代码

public class BitMap {
	final int bit = 10000000;
	private int[] arr = new int[bit];
	private int[] mSort = new int[bit];

	public BitMap() {
		// TODO Auto-generated constructor stub
		Random random = new Random();
		for (int i = 0; i < bit; i++) {
			arr[i] = -1;
			arr[i] = random.nextInt(bit-1);
			mSort[i] = 0;
		}
	}

	public void sort() {
		for(int i = 0; i< bit; i++){
			if(arr[i] != -1)
			mSort[arr[i]] ++;
		}
	}

	public void print(){
		for(int i = 0 ; i < bit ;i++){
			if(mSort[i] > 0)
				System.out.println(i+"*"+mSort[i]);
		}
	}

测试

可见排序时间暴涨了一倍多,但还是在可接受范围内。

version 2.1

既然用到了interger,而interger又有2147483647这么大的范围,我们可以尝试将排位范围也提高到200000000即不超过2亿个9位整数(为什么不用20亿?因为java会提示堆栈内存超限)。

测试

此时时间达到了4s左右,但是对比我们排序的2亿个数,成绩应该很可喜了。

分段输出

由于如此庞大的数据除了导出到文本文件里,几乎不可能实时查看,所以我们对某一区间进行测试输出。

  1. 0~50
  2. 1234567~1234600

间接反映了随机数生成方法还是比较稳定的。。。(笑)

总结

除了对Jon Bentley的佩服还是佩服。

posted @ 2018-03-28 19:51  秦川德利齐  阅读(274)  评论(0编辑  收藏  举报