高级排序算法

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qiyei2009/article/details/79825958

1 前言

对于基本排序算法来说,时间复杂度一般都是O(n^2)。而高级排序算法的时间复杂度一般都是O(nlogn)。高级排序算法主要针对基本排序算法进行优化。下面介绍几种常见的高级排序算法,希尔排序,归并排序,快速排序,堆排序

2 希尔排序

希尔排序是插入排序的一个改进,它主要是用一个递增序列来使数组进行一个分组,然后对每组进行一个插入排序
例如递增序列 h = 3*h + 1;那么
先找到最大的递增序列 h = n/3
接着我们就可以对
0 0+h 0+2*h …. 0 + k*h
1 1+h 1+2*h …. 1 + k*h
…..
i i+h i+2*h …
等进行插入排序,当h=1时,排序就完成了
相关算法见下:

@Override
public void sort(Comparable[] array) {
    int length = array.length;
    int h = 0;

    /**
     * 递增序列 3*k + 1
     */
    while (h < length / 3){
        h = 3 * h + 1;
    }
    //间隔为n的插入排序
    while (h >= 1){
        for (int i = h; i < length ; i++){
            for (int j = i ;j >= h ; j -= h){
                //将a[i] a[i-h] a[i -2h] 做插入排序
                if (less(array[j],array[j-h])){
                    exch(array,j,j-h);
                }
            }
        }
        h = h/3;
    }

}

可以看到,希尔排序就是对间隔为h数进行插入排序。

3 归并排序

归并排序采用了一种分治和递归思想,它是将一个数组分为两部分,然后分别对两部分进行排序,然后将两个有序的数组进行归并成一个数组的过程。对两个数组的排序又可以采用递归的思想,分别进行拆分成两个数组,进行排序。然后再进行归并。
归并排序算法如下:自顶向下

/**
 * @author Created by qiyei2015 on 2018/3/19.
 * @version: 1.0
 * @email: [email protected]
 * @description: 归并排序
 */
public class MergeSort extends BaseSort{

    private Comparable[] aux;
    private static final int M = 15;

    @Override
    public void sort(Comparable[] array) {
        aux = new Comparable[array.length];

        int lo = 0;
        int hi = array.length - 1;
        sort(array,lo,hi);
    }

    /**
     * 归并排序
     * @param array
     * @param lo
     * @param hi
     */
    private void sort(Comparable[]array,int lo,int hi){
        if (hi - lo <0){
            return;
        }
        int mid = lo + (hi - lo)/2;
        //排左半边
        sort(array,lo,mid);
        //排右半边
        sort(array,mid + 1,hi);
        //归并
        merge(array,lo,mid,hi);
    }

    /**
     * 数组合并
     * @param array
     * @param lo
     * @param mid
     * @param hi
     */
    private void merge(Comparable[] array,int lo,int mid,int hi){
        int i = lo;
        int j = mid + 1;

        //将数组array复制到aux中
        for (int k = lo ;k <= hi ; k++){
            aux[k] = array[k];
        }

        //aux[lo..mid] aux[mid+1..hi]
        for (int k = lo ; k <= hi ; k++){
            if (i > mid){
                //i 超过mid,说明左半边用完,取右半边
                array[k] = aux[j++];
            }else if (j > hi){
                //j 超过hi,说明右半边用完,取左半边
                array[k] = aux[i++];

            }else if (less(aux[i],aux[j])){
                //i 比j小,取i
                array[k] = aux[i++];
            }else {
                array[k] = aux[j++];
            }
        }
    }

}

可以看到,归并排序的核心过程就是这个归并的过程。其思想是用一个临时数组来存取元素。然后分别用两个指针来计数两个分段数组。比较这两个数组,将较小的赋值到数组中。这样就完成了归并过程

优化点:
1 在hi – lo <= M M为15等,也就是分割到一个大小差不多为16个数组的时候,可以考虑采用插入排序
2 在分割之后,如果本身数组已经有序的情况,例如a[mid] <= mid[mid+1]时,就不用归并了,因此优化如下:

* 归并排序
 * @param array
 * @param lo
 * @param hi
 */
private void sort(Comparable[]array,int lo,int hi){
    if (hi - lo <= M){
        new InsertionSort().sort(array,lo,hi);
        return;
    }
    int mid = lo + (hi - lo)/2;
    //排左半边
    sort(array,lo,mid);
    //排右半边
    sort(array,mid + 1,hi);
    //归并
    if (array[mid].compareTo(array[mid + 1]) > 0){
        merge(array,lo,mid,hi);
    }
}

对于链表等,可以考虑采用自底向上来进行归并。

另外,堆排序是一种稳定的排序

4 快速排序

快速排序是一种非常经典和常用的排序,被誉为20世纪最伟大的排序。快速排序的思想和归并类似,将数组进行一个切分。这样数组就分为三部分了
a[0..v-1] a[v] a[v+1…]使其a[v]之前的数小于a[v] a[v]之后的数大于a[v]。然后对剩余的数继续进行快速排序,快速排序也用到了分治和递归的思想。
快速排序的实现如下:

/**
 * @author Created by qiyei2015 on 2018/3/25.
 * @version: 1.0
 * @email: [email protected]
 * @description:
 */
public class QuickSort extends BaseSort {
    @Override
    public void sort(Comparable[] array) {
        int lo = 0;
        int hi = array.length - 1;
        sort(array,lo,hi);
}

    /**
     * 快速排序,分治 递归
     * @param array
     * @param lo
     * @param hi
     */
    private void sort(Comparable[] array,int lo,int hi){
        //递归结束条件
        if (hi - lo <0){
            return;
        }
        //parttion 已经处于该位置上的有序了,因此该位置上的数不用排序
        int parttion = parttion(array,lo,hi);
        sort(array,lo,parttion -1);
        sort(array,parttion + 1,hi);
}

    /**
     * 找到切分点,将数组分为 a[lo,j-1] a[j] a[j+1,hi]三部分,其中a[lo..j-1] < a[j],a[j+1..hi] > a[j]
     * @param array
     * @param lo
     * @param hi
     * @return
     */
    private int parttion(Comparable[] array, int lo, int hi){
        Comparable v = array[lo];
        int j = lo;
        //找到切分点 a[lo..j-1] < a[j],a[j+1..hi] > a[j]
        for (int i = lo + 1; i <= hi ;i++){
            //如果a[i]比v小,就交换j+1和i,并且j++
            if (less(array[i],v)){
                exch(array,j + 1,i);
                j++;
            }
        }
        exch(array,lo,j);
        return j;
    }

快速排序的关键在于切分,切分就是在v的位置a[v]已经排好序。切分思想如下:
以第一个元素a[lo] v为例,将该元素排好序,从左lo + 1到右扫描数组,如果a[i]比v小,就交换a[i]与j+1。并且j++。这样比v小的数就从lo + 1到j了。而大于等于v的数就排到a[j]及其后面去了。最后一个小于v的元素是a[j]然后将其与a[lo]交换,这样a[lo…j-1]的元素就小于a[j]而其后的元素就大于等于a[j]了。

优化点:
1 切分以后的元素可以考虑采用插入排序
2 对于切分元素a[lo]进行随机化。
3 对于有序的数组可以采用双路排序
4 对于大量范围很小的数据采用三路快速排序
5 交换采用赋值操作,减少交换的次数

扩展
怎么在N个数中找到第M个大小的数?
可以利用切分的思想。

5 堆排序

由于堆的性质,根节点的数总是大于(小于)子结点,我们以最大堆为例,堆排序就是我们将数组从后往前,依次取堆的最大的元素(根节点),然后不断的调整堆的结构。最后将数组遍历完毕时,也完成了排序。由于调整堆结构的复杂度为O(logn),因此堆排序的时间复杂度也是O(nlogn)。

/**
 * 堆排序
 * @param array
 */
@Override
public void sort(Comparable[] array) {
    MaxPQ<Integer> maxPQ = new MaxPQ(array.length);
    for (int i = 0 ; i < array.length ; i++){
        maxPQ.insert((Integer) array[i]);
    }
    for (int i = array.length - 1 ; i >= 0 ; i--){
        array[i] = maxPQ.delMax();
    }
}

其中MaxPQ是一个最大堆

以上就是一些高级排序算法,除了希尔排序算法的时间复杂度不好评估外(但是也小于O(n^2))。其他排序算法的平均时间复杂度都是O(nlogn)

猜你喜欢

转载自blog.csdn.net/qiyei2009/article/details/79825958
今日推荐