码迷,mamicode.com
首页 > 编程语言 > 详细

排序算法比较及其应用

时间:2017-04-20 23:54:01      阅读:548      评论:0      收藏:0      [点我收藏+]

标签:isa   who   数据类型   amp   nlogn   str   between   复杂   distance   

一、将各种数据排序

只要实现了Comparable接口的数据类型就可以被排序。

但要使算法能够灵活地用不同字段进行排序,则是后续需要考虑的问题。

1、指针排序

在Java中,指针操作是隐式的,排序算法操作的总是数据引用,而不是数据本身。

 

2、键不可变

如果在排序后,用例还可以改变键值,那么数组很可能就不是有序的了。类似,优先队列也会乱套。

Java中,可以用不可变数据类型作为键来避免这个问题,如String,Integer,Double和File都是不可变的。

 

3、廉价交换

使用引用的另一个好处是算法不必移动整个元素,对于元素大键值小的数组来说将会省下很多操作成本。

因为比较只需要访问元素的一小部分,在整个排序过程中,大部分不会被访问到。

因此对于任意大小的元素,使用引用,使得一般情况下交换和比较的成本几乎相同。

如果键值很长,那么交换的成本甚至会低于比较的成本。

 

4、多种排序方法

实际应用中,用户希望根据情况对一组对象按照不同的方式进行排序。

Java的Comparator接口允许在一个类中实现多种排序方法。通过将Comparator接口对象传递给sort方法,再传递给less方法即可。

Comparator接口实现举例如下:

    public class WhoOrder implements Comparator<Transaction> {

        @Override
        public int compare(Transaction v, Transaction w) {
            //add code
            return 0;
        }
        
    }

 

sort举例如下:

    public static void sort(Object[] a, Comparator c) {
        int N = a.length;
        for(int i = 1; i < N; i++)
            for(int j = i; j > 0 && less(c, a[j], a[j-1]); j--)
                exch(a, j, j-1);
    }
    
    private static boolean less(Comparator c, Object v, Object w) {
        return c.compare(v, w) < 0;
    }
    
    private static void exch(Object[] a, int i, int j) {
        Object t = a[i];
        a[i] = a[j];
        a[j] = t;
    }

 

 

Comparator接口允许为任意数据定义任意多种排序方法。用Comparator替代Comparable接口可以更好地将数据类型的定义和两个该数据类型对象应该如何比较的定义区分开来。

比如,相对字符串数组a,进行忽略大小写排序,可以使用String类中的CASE_INSENSITIVE_ORDER比较器Comparator,并传递给sort函数:

Insertion.sort(a, String.CASE_INSENSITIVE_ORDER)

 

 

5、多键

在实际应用中,一个元素的多个属性都可能被用作排序的键。

要实现这种灵活性,Comparator接口正合适,我们可以在数据类型中定义多种比较器(Comparator)。

例如:

public class Transaction implements Comparable<Transaction> {
    private final String  who;      // customer
    private final Date    when;     // date
    private final double  amount;   // amount

    /**
     * Compares two transactions by customer name.
     */
    public static class WhoOrder implements Comparator<Transaction> {

        @Override
        public int compare(Transaction v, Transaction w) {
            return v.who.compareTo(w.who);
        }
    }

    /**
     * Compares two transactions by date.
     */
    public static class WhenOrder implements Comparator<Transaction> {

        @Override
        public int compare(Transaction v, Transaction w) {
            return v.when.compareTo(w.when);
        }
    }

    /**
     * Compares two transactions by amount.
     */
    public static class HowMuchOrder implements Comparator<Transaction> {

        @Override
        public int compare(Transaction v, Transaction w) {
            return Double.compare(v.amount, w.amount);
        }
    }
}

 这样定义之后,想让Transaction按照时间排序,可以调用

Insertion.sort(a, new Transaction.WhenOrder()) 

 

6、稳定性

如果一个排序算法能够保留数组中重复元素的相对位置的话,则这个排序算法使稳定的。

插入排序归并排序是稳定的,选择排序希尔排序快速排序堆排序则不是稳定的。

二、排序算法的对比

1、插入排序

稳定、原地排序

时间复杂度:最坏~N2/2、最好~N、平均~N2/4

空间复杂度:1

交换次数:最坏~N2/2、最好0、平均~N2/4

注:取决于输入元素的排列情况

http://www.cnblogs.com/songdechiu/p/6610515.html

2、选择排序

不稳定、原地排序

时间复杂度:N2(~N2/2)

空间复杂度:1

交换次数:N

http://www.cnblogs.com/songdechiu/p/6609896.html

3、希尔排序

不稳定、原地排序

时间复杂度:达不到平方级别、最坏和N3/2成正比(未知

空间复杂度:1

http://www.cnblogs.com/songdechiu/p/6611340.html

4、快速排序

不稳定、原地排序

时间复杂度:最好~NlogN、最坏~N2平均1.39NlogN

空间复杂度:最好logN,最坏N,平均logN

运行效率由概率提供保证

http://www.cnblogs.com/songdechiu/p/6629539.html

5、三向切分快速排序

不稳定、原地排序

时间复杂度:最好N、最坏~N2平均~NlogN

空间复杂度:最好1、最坏N、平均logN

运行效率由概率提供保证

http://www.cnblogs.com/songdechiu/p/6629539.html

6、归并排序

稳定非原地排序

时间复杂度:1/2NlgN至NlgN

空间复杂度:N

数组访问次数:6NlgN

http://www.cnblogs.com/songdechiu/p/6607341.html

http://www.cnblogs.com/songdechiu/p/6607720.html

7、堆排序

不稳定、原地排序

时间复杂度:2NlogN + 2N(最坏)

空间复杂度:1

数组元素交换次数:NlogN + N(最坏)

http://www.cnblogs.com/songdechiu/p/6736502.html

 

结论:快速排序是最快的通用排序算法,因为其内循环指令少,而且还能利用缓存(顺序访问数据),其增长数量级为~cNlogN。特别是使用三切分之后,快排对某些特殊输入其复杂度变为线性级别。

 

实际应用中,大多数情况下,快速排序是最好的选择。

但是如果稳定性很重要而且空间又不是问题,那么归并排序是最好的。

 

三、问题规约

1、找出重复元素

首先将数组排序,然后遍历有序的数组,记录连续出现的重复元素即可。

 

2、排列

一个排列就是一组N个整数的数组,其中0到N-1的每个数都只出现一次。

两个排列之间的Kendall tau距离就是两组排列中的逆序数对。如0 3 1 6 2 5 4和1 0 3 6 4 2 5的Kendall tau距离是4,因为0-1,3-1,2-4,5-4这四对数字的相对顺序不同。

Kendall tau距离就是逆序数对的数量。

实现:http://algs4.cs.princeton.edu/25applications/KendallTau.java.html

public class KendallTau {

    // return Kendall tau distance between two permutations
    public static long distance(int[] a, int[] b) {
        if (a.length != b.length) {
            throw new IllegalArgumentException("Array dimensions disagree");
        }
        int n = a.length;

        int[] ainv = new int[n];
        for (int i = 0; i < n; i++)
            ainv[a[i]] = i;

        Integer[] bnew = new Integer[n];
        for (int i = 0; i < n; i++)
            bnew[i] = ainv[b[i]];

        return Inversions.count(bnew);
    }

}

两个排列a和b逆序数对跟以前归并排序中求一个未排好序的排列相对排好序的排列的逆序数对有点不同。

归并排序中是默认跟标准序列(排好序)即0 1 2 3 4 5 6进行比较,这里是a跟b进行比较,我们以b为标准序列

所以我们要转换成一个序列跟标准序列之间的逆序数对,再将该序列传给归并排序中的求解方法。

 

 以方便对上述代码进行解释,做出以下假设

a:0 3 1 6 2 5 4

b:1 0 3 6 4 2 5

其中将a[i]称为key,i称为index;b[j]也为key,j为index

上述代码的思路主要思路是转化为index序列之间的比较,以b为标准序列,其index序列0 1 2 3 4 5 6,再求出b[i](0<= i <= 6)在数组a中相应的index序列

ainv是a的逆数组,即存储着数组a的key所在的index,即ainv[k] = i代表着a[i] = k。

 

当key为b[0] = 1时,其索引为ainv[b[0]]即ainv[1] = 2,以此类推最后求出a相对于b的索引序列2 0 1 3 6 4 5

最后将这个相对索引序列2 0 1 3 6 4 5传给归并排序中的求解方法即可,求出为4。

 

以下为归并排序中邱逆序数对的方法:

http://algs4.cs.princeton.edu/22mergesort/Inversions.java.html

public class Inversions {

    // do not instantiate
    private Inversions() { }

    // merge and count
    private static long merge(int[] a, int[] aux, int lo, int mid, int hi) {
        long inversions = 0;

        // copy to aux[]
        for (int k = lo; k <= hi; k++) {
            aux[k] = a[k]; 
        }

        // merge back to a[]
        int i = lo, j = mid+1;
        for (int k = lo; k <= hi; k++) {
            if      (i > mid)           a[k] = aux[j++];
            else if (j > hi)            a[k] = aux[i++];
            else if (aux[j] < aux[i]) { a[k] = aux[j++]; inversions += (mid - i + 1); }
            else                        a[k] = aux[i++];
        }
        return inversions;
    }

    // return the number of inversions in the subarray b[lo..hi]
    // side effect b[lo..hi] is rearranged in ascending order
    private static long count(int[] a, int[] b, int[] aux, int lo, int hi) {
        long inversions = 0;
        if (hi <= lo) return 0;
        int mid = lo + (hi - lo) / 2;
        inversions += count(a, b, aux, lo, mid);  
        inversions += count(a, b, aux, mid+1, hi);
        inversions += merge(b, aux, lo, mid, hi);
        assert inversions == brute(a, lo, hi);
        return inversions;
    }


    /**
     * Returns the number of inversions in the integer array.
     * The argument array is not modified.
     * @param  a the array
     * @return the number of inversions in the array. An inversion is a pair of 
     *         indicies {@code i} and {@code j} such that {@code i < j}
     *         and {@code a[i]} > {@code a[j]}.
     */
    public static long count(int[] a) {
        int[] b   = new int[a.length];
        int[] aux = new int[a.length];
        for (int i = 0; i < a.length; i++)
            b[i] = a[i];
        long inversions = count(a, b, aux, 0, a.length - 1);
        return inversions;
    }
}

 

3、优先队列规约

两个规约为优先队列操作问题得例子:

TopM,在输入流中找到前M个最大的元素。

MultiWay,将M个有序输入流归并为一个有序输入流。

 

4、中位数和顺序统计

找到一组数中的第k小的元素(中位数是特殊情况)。

可以规约为快排中的切分方法。

    public static <Key extends Comparable<Key>> Key select(Key[] a, int k) {
        if (k < 0 || k >= a.length) {
            throw new IndexOutOfBoundsException("Selected element out of bounds");
        }
        StdRandom.shuffle(a);
        int lo = 0, hi = a.length - 1;
        while (hi > lo) {
            int i = partition(a, lo, hi);
            if      (i > k) hi = i - 1;
            else if (i < k) lo = i + 1;
            else return a[i];
        }
        return a[lo];
    }

 

 

平均上,基于切分的选择算法的运行时间是线性级别的。

 

四、排序应用

排序算法比较及其应用

标签:isa   who   数据类型   amp   nlogn   str   between   复杂   distance   

原文地址:http://www.cnblogs.com/songdechiu/p/6741533.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!