经典算法题14-外排序

引入

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

这时我们就要用外排序(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);
    }

}

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

结果

这里写图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值