在秋招的笔试中遇到过好几次大数据量排序的问题,今天又在编程珠玑上看到了类似的问题,通过以前的一些积累,写几点我对大数据排序的几种方法的一些见解。
位图法
位图法是我在编程珠玑上看到的一种比较新颖的方法,思路比较巧妙效率也很高。
使用场景举例:对2G的数据量进行排序,这是基本要求。
数据:1、每个数据不大于8亿;2、数据类型位int;3、每个数据最多重复一次。
内存:最多用200M的内存进行操作。
首先对占用的内存进行判断,每个数据不大于8亿,那么8亿是一个什么概念呢。
**1 byte = 8 bit(位)
1024 byte = 8*1024 bit = 1k
1024 k = 8*1024*1024 bit = 1M = 8388608 bit**
也就是1M=8388608位
而位图法的基本思想就是利用一位代表一个数字,例如3位上为1,则说明3在数据中出现过,若为0,则说明3在数据中没有出现过。所以当题目中出现每个数据最多重复一次这个条件时,我们可以考虑使用位图法来进行大数据排序。
那么假如使用位图法来进行这题的排序,内存占用多少呢。由题目知道每个数据不大于8亿,那么我们就需要8亿位,占用800000000/8388608=95M的空间,满足最多使用200M内存进行操作的条件,这也是这题能够使用位图法来解决的一个基础。
堆排序法
堆排序是4种平均时间复杂度为nlogn的排序方法之一,其优点在于当求M个数中的前n个最大数,和最小数的时候性能极好。所以当从海量数据中要找出前m个最大值或最小值,而对其他值没有要求时,使用堆排序法效果很好。
使用场景:从1亿个整数里找出100个最大的数
步骤:
1.读取前100个数字,建立最大值堆。(这里采用堆排序将空间复杂度讲得很低,要排序1亿个数,但一次性只需读取100个数字,或者设置其他基数,不需要1次性读完所有数据,降低对内存要求)
2、依次读取余下的数,与最大值堆作比较,维持最大值堆。可以每次读取的数量为一个磁盘页面,将每个页面的数据依次进堆比较,这样节省IO时间。
3.将堆进行排序,即可得到100个有序最大值。
堆排序是一种常见的算法,但了解其的使用场景能够帮助我们更好的理解它。
“`
public abstract class HeapSort {
public abstract boolean compare(E value1, E value2);//value1小于value2则返回true
public boolean heapSort(List<E> list){//排序
return heapSort(list, list.size());
}
public boolean heapSort(List<E> list, int n){
if(null == list || 0 == list.size()){
return false;
}
if(!heapCreate(list, n)){
return false;
}
for(int i = n; i > 0; --i){
swap(list, 0, i - 1);
heapAdjust(list, 0, i - 1);
}
return true;
}
public boolean heapCreate(List<E> list, int length){ //创建小根堆
if(null == list || 0 == list.size()){
return false;
}
for(int i = (length / 2 - 1); i >= 0; --i){
if(!heapAdjust(list, i, length)){
return false;
}
}
return true;
}
public boolean heapAdjust(List<E> list, int middle, int length){//调整堆,使其满足小根堆的条件
if(null == list || 0 == list.size()){
return false;
}
E temp = list.get(middle);
for(int i = (2 * middle + 1); i < length; i *= 2){
if(i < (length - 1) && !this.compare(list.get(i), list.get(i + 1))){
++i;
}
if(this.compare(temp,list.get(i))){
break;
}
list.set(middle, list.get(i));
middle = i;
}
list.set(middle, temp);
return true;
}
public void swap(List<E> list, int i, int j){//数据交换
E temp = list.get(i);
list.set(i, list.get(j));
list.set(j, temp);
}
}
“
较为通用的分治策略
分治策略师对常见复杂问题的一种万能的解决方法,虽然很多情况下,分治策略的解法都不是最优解,但是其通用性很强。
分治法的核心就是将一个复杂的问题通过分解抽象成若干个简单的问题。
应用场景:10G的数据,在2G内存的单台机器上排序的算法
我的想法,这个场景既没有介绍数据是否有重复,也没有给出数据的范围,也不是求最大的个数。而通过分治虽然可能需要的io次数很多,但是对解决这个问题还是具有一定的可行性的。
步骤:
1、从大数据中抽取样本,将需要排序的数据切分为多个样本数大致相等的区间,例如:1-100,101-300…
2、将大数据文件切分为多个小数据文件,这里要考虑IO次数和硬件资源问题,例如可将小数据文件数设定为1G(要预留内存给执行时的程序使用)
3、使用最优的算法对小数据文件的数据进行排序,将排序结果按照步骤1划分的区间进行存储
4、对各个数据区间内的排序结果文件进行处理,最终每个区间得到一个排序结果的文件
5、将各个区间的排序结果合并.
通过分治将大数据变成小数据进行处理,再合并。