排序算法比较及其应用

一、将各种数据排序

只要实现了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];
    }

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

四、排序应用

时间: 2024-12-25 00:42:56

排序算法比较及其应用的相关文章

经典排序算法 - 冒泡排序Bubble sort

 原文出自于 http://www.cnblogs.com/kkun/archive/2011/11/23/bubble_sort.html 经典排序算法 - 冒泡排序Bubble sort 原理是临近的数字两两进行比较,按照从小到大或者从大到小的顺序进行交换, 这样一趟过去后,最大或最小的数字被交换到了最后一位, 然后再从头开始进行两两比较交换,直到倒数第二位时结束,其余类似看例子 例子为从小到大排序, 原始待排序数组| 6 | 2 | 4 | 1 | 5 | 9 | 第一趟排序(外循环) 第

选择排序 —— 排序算法系列

假设我们有如下一个数组: 使用选择排序算法对这个数组进行排序,步骤如下: 第 1 次 在下标0到6之间找到最小的数字,我们可以发现最小的数字是15,它在下标为4的位置上: 把下标4上面的数字跟下标0上面的数字互换,得到排序如下图的数组: 第 2 次 在下标1到6之间找到最小的数字,我们可以发现最小的数字是33,它在下标为5的位置上: 把下标5上面的数字跟下标1上面的数字互换,得到排序如下图的数组: 第 3 次 在下标2到6之间找到最小的数字,我们可以发现最小的数字是48,它在下标为5的位置上:

排序算法Java版,以及各自的复杂度,以及由堆排序产生的top K问题

常用的排序算法包括: 冒泡排序:每次在无序队列里将相邻两个数依次进行比较,将小数调换到前面, 逐次比较,直至将最大的数移到最后.最将剩下的N-1个数继续比较,将次大数移至倒数第二.依此规律,直至比较结束.时间复杂度:O(n^2) 选择排序:每次在无序队列中"选择"出最大值,放到有序队列的最后,并从无序队列中去除该值(具体实现略有区别).时间复杂度:O(n^2) 直接插入排序:始终定义第一个元素为有序的,将元素逐个插入到有序排列之中,其特点是要不断的 移动数据,空出一个适当的位置,把待插

排序算法总结

各种排序算法总结  排序算法  插入排序 冒泡排序  选择排序  归并排序  快速排序 堆排序  计数排序  基数排序  桶排序  思想  构建有序序列 两两交换 每次找一个最小值 分治法思想 分治法思想 最小堆.最大堆 数字本身的属性  对数据选择多种基数  函数的映射关系.Hash  数据结构  数组  数组  数组  数组 不定   数组 数组 数组  数组  最差时间复杂度 O(n^2)   O(n^2)   O(n^2)   O(n*lgn)  O(n^2).改进O(n*lgn)  O

七大常见排序算法总结

文档版本 开发工具 测试平台 工程名字 日期 作者 备注 V1.0 2016.04.06 lutianfei none V1.1 2016.07.16 lutianfei 增加了归并排序说明 V2.0 2016.07.19 lutianfei 完善了排序算法的总结 排序另一种分法 外排序:需要在内外存之间多次交换数据才能进行 内排序: 插入类排序 直接插入排序 希尔排序 选择类排序 简单选择排序 堆排序 交换类排序 冒泡排序 快速排序 归并类排序 归并排序 排序方法 平均情况 最好情况 最坏情况

数据结构——各排序算法的比较

1.从时间复杂度比较  从平均时间复杂度来考虑,直接插入排序.冒泡排序.直接选择排序是三种简单的排序方法,时间复杂度都为O(n2),而快速排序.堆排序.二路归并排序的时间复杂度都为O(nlog2n),希尔排序的复杂度介于这两者之间.若从最好的时间复杂度考虑,则直接插入排序和冒泡排序的时间复杂度最好,为O(n),其它的最好情形同平均情形相同.若从最坏的时间复杂度考虑,则快速排序的为O(n2),直接插入排序.冒泡排序.希尔排序同平均情形相同,但系数大约增加一倍,所以运行速度将降低一半,最坏情形对直接

八种排序算法

最近一段时间自己在研究各种排序算法,于是自己写了一个八种排序算法的集合: /************************************************************************* > Copyright (c)2014 stay hungry,stay foolish !!! > File Name: sort.cpp > Author: kanty > Mail: [email protected] > Created Time:

排序算法 之 快速排序

快速排序是基于分治思想的一种排序算法,就像该方法的名字一样,速度比较快,所以叫做快速排序:它的平均时间复杂度为O(N*logN),最坏时间复杂度为O(n2),由于快速排序在序列元素数量多的时候速度比较快,所以很多语言内置的排序方法也是用快速排序实现的.快速排序也有很多优化的版本,比如在排序时基数的选择等等-下面就说一下一般的快速排序的实现. 基本思想: 快速排序的基本思想就是,先从待排序的序列中任选一个元素作为基数,然后将序列中的其他小于基数的元素放在基数的左边,大于或等于基数的元素放在基数的右

排序算法的JS实现

排序算法是基础算法,虽然关键在于算法的思想而不是语言,但还是决定借助算法可视化工具结合自己常用的语言实现一下 1.冒泡排序 基本思路:依次比较两两相邻的两个数,前面数比后面数小,不变.前面数比后面数大,交换顺序.一轮下来,最后的一个数是最大的数. 外循环每增加一次,内循环减少一次. 图形展示: function bubbleSort(arr){ for (var i = 0; i < arr.length; i++) { for (var j = 0; j< arr.length-i-1; j