经典算法题14-外排序

来源:互联网 发布:我的世界手机版生存js 编辑:程序博客网 时间:2024/06/06 15:40

引入

我们要处理一个大文件,对其中的数值排序,一般我们想到的方法就是用排序算法,像快速排序、归并排序、选择排序、堆排序、冒泡排序等。但是这些排序算法使用的前提是需要把数据读入到内存,现在大文件太大,内存装不下,如何处理?

这时我们就要用外排序(External sorting)

介绍

外排序是指能够处理极大量数据的排序算法。归并(merge)排序算法中用到了分治思想,一个大问题我们可以采取分而治之,各个突破,当子问题解决了,大问题也就搞定了。外排序也采用的是一种“排序-归并”的策略。在排序阶段,先读入能放在内存中的数据量,将其排序输出到一个临时文件,依此进行,将待排序数据组织为多个有序的临时文件。然后在归并段阶将这些临时文件组合为一个大的有序文件,也即完成排序结果。过程如下图所示。
这里写图片描述

图中这里有个batch容器,这个容器我是基于性能考虑的,当batch=n时,我们定时刷新到文件中,保证内存有足够的空间。

外排序的一个例子是外归并排序(External merge sort),它读入一些能放在内存内的数据量,在内存中排序后输出为一个顺串(即是内部数据有序的临时文件),处理完所有的数据后再进行归并,这样就得到了完整文件的排序结果。维基百科(外排序)有这个简单实例,可以参考。

当然可以用其他常规排序方式(如快速排序、堆排序、归并排序等方法)在内存中完成小文件的排序。

简而言之:外排序实现是使用堆来生成若干个顺串,然后使用多路归并算法来生成最终排序好的内容。

败者树,赢者树

胜者树与败者树是完全二叉树。就像是参加比赛一样,每个选手有不同的实力,两个选手PK,实力决定胜负,晋级下一轮,经过几轮之后,就能得到冠军。

胜者树和败者树可以在log(n)的时间内找到最值,但是如果只是找最值,有点大材小用了,中间节点记录的标号就没有意义了。其意义在于,任何一个叶子节点的值改变后,利用中间节点的信息,还是能够快速的找到最值。以后细讲他们的实现。

外排序编码

 // recursive method to merge the lists until we are left with a    // single merged list    private File process(ArrayList<File> list) throws IOException {        if (list.size() == 1) {            return list.get(0);        }        ArrayList<File> inter = new ArrayList<File>();        for (Iterator<File> itr = list.iterator(); itr.hasNext(); ) {            File one = itr.next();            if (itr.hasNext()) {                File two = itr.next();                inter.add(merge(one, two));            } else {                return one;            }        }        return process(inter);    }    /**     * Splits the original file into a number of sub files.     */    private ArrayList<File> split(File file) throws IOException {        ArrayList<File> files = new ArrayList<File>();        int[] buffer = new int[BUFFER_SIZE];        FileInputStream fr = new FileInputStream(file);        boolean fileComplete = false;        while (!fileComplete) {            int index = buffer.length;            for (int i = 0; i < buffer.length && !fileComplete; i++) {                buffer[i] = readInt(fr);                if (buffer[i] == -1) {                    fileComplete = true;                    index = i;                }            }            if (buffer[0] > -1) {                Arrays.sort(buffer, 0, index);                File f = new File("set" + new Random().nextInt());                FileOutputStream writer = new FileOutputStream(f);                for (int j = 0; j < index; j++) {                    writeInt(buffer[j], writer);                }                writer.close();                files.add(f);            }        }        fr.close();        return files;    }    /**     * Merges two sorted files into a single file.     *     * @param one     * @param two     * @return     * @throws IOException     */    private File merge(File one, File two) throws IOException {        FileInputStream fis1 = new FileInputStream(one);        FileInputStream fis2 = new FileInputStream(two);        File output = new File("data/merged" + new Random().nextInt());        FileOutputStream os = new FileOutputStream(output);        int a = readInt(fis1);        int b = readInt(fis2);        boolean finished = false;        while (!finished) {            if (a != -1 && b != -1) {                if (a < b) {                    writeInt(a, os);                    a = readInt(fis1);                } else {                    writeInt(b, os);                    b = readInt(fis2);                }            } else {                finished = true;            }            if (a == -1 && b != -1) {                writeInt(b, os);                b = readInt(fis2);            } else if (b == -1 && a != -1) {                writeInt(a, os);                a = readInt(fis1);            }        }        os.close();        return output;    }

具体完整的代码见我的github。
https://github.com/shibing624/BlogCode/blob/master/src/main/java/xm/math/sort/extsort/ExternalSorter.java

结果

这里写图片描述

Redis实现

现在有了Redis做key-value处理,可以直接把大文件的数据加载到redis,使用其自带的排序,也可以实现排序功能,而且速度超快,下面的代码是用jedis实现了该功能。

package xm.nosql;import redis.clients.jedis.Jedis;import java.io.BufferedReader;import java.io.File;import java.io.FileInputStream;import java.io.InputStreamReader;import java.util.List;/** * @author xuming */public class SortDemo {    public static void main(String[] args) {        Jedis jedis;        jedis = new Jedis("127.0.0.1", 6379);        jedis.flushDB();        //jedis 排序        //注意,此处的rpush和lpush是List的操作。是一个双向链表(但从表现来看的)        jedis.del("b");//先清除数据,再加入数据进行测试        jedis.rpush("b", "1");        jedis.lpush("b", "6");        jedis.lpush("b", "3");        jedis.lpush("b", "9");        try {            File f = new File("data/test_sort.txt");            BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(f), "utf-8"));            String readline;            while ((readline = br.readLine()) != null) {                jedis.lpush("b", readline.trim());            }            br.close();        } catch (Exception e) {            e.printStackTrace();        }        System.out.println(jedis.sort("b")); //[1, 3, 6, 9]  //输入排序后结果        List<String> set = jedis.sort("b");        set.forEach(System.out::println);    }}

具体完整的代码见我的github。https://github.com/shibing624/BlogCode/blob/master/src/main/java/xm/nosql/SortDemo.java

结果

这里写图片描述

0 0