算法-选择排序,插入排序,希尔排序,归并排序,快速排序

排序算法模板

public class Example {
    public static void sort(Comparable[] a){
        //此处为排序算法
        //默认由小到大排序
    }

    private static boolean less(Comparable v, Comparable w){
        return v.compareTo(w) < 0;
    }

    private static void exch(Comparable[] a, int i, int j){
        Comparable t = a[i];
        a[i] = a[j];
        a[j] = t;
    }

    private static void show(Comparable[] a){
        for (int i = 0; i < a.length; i++){
            System.out.print(a[i] + " ");
        }
        System.out.println();
    }

    public static boolean isSored(Comparable[] a){
        for (int i = 1; i < a.length; i++){
            if (less(a[i], a[i - 1])){
                return false;
            }
        }
        return true;
    }

    public static void main(String[] args){
        Integer[] a = {1, 4, 2, 5, 2, 8};
        sort(a);
        assert isSored(a);
        show(a);
    }
}

选择排序

选择排序不断选择剩余元素之中的最小者,并交换它到对应的位置。它是第几小的元素,就把它交换到数组的第几个位置。

    public static void sort(Comparable[] a){
        int N = a.length;
        for (int i = 0;i < N;i++){
            int min = i;
            for (int j = i + 1;j < N;j++){
                if(less(a[j], a[min])){
                    min = j;
                }
            }
            exch(a, i, min);
        }
    }

选择排序十分稳定,运行时间和输入无关,并且数据移动是最少的。
在任何情况下,需要N(N-1)/2次比较,N次交换。
 
 

插入排序

插入排序将每个元素插入到左侧已经有序的元素中。

    public static void sort(Comparable[] a){
        int N = a.length;
        for (int i = 1; i < N; i++){
            for (int j = i; j > 0 && less(a[j], a[j - 1]); j--){
                exch(a, j, j - 1);
            }
        }
    }

插入排序不稳定,运行时间与输入元素的初始顺序关系大。
最坏情况下(所有元素逆序排列),需要N(N-1)/2次比较,N(N-1)/2次交换。
平均情况下(随机排列),需要~N^2/4次比较,~N^2/4次交换。
最好情况下(已经有序),需要N-1次比较,0次交换。
 
 

希尔排序

希尔排序使数组中任意间隔为h(步长)的元素都是有序的,如果h很大,就能将元素移动到很远的地方,为实现更小的h有序创造方便。
排序之初,各个子数组都很短,排序之后,子数组都是部分有序的。这两种情况都很适合插入排序。

    public static void sort(Comparable[] a){
        int N = a.length;
        int h = 1;
        while (h < N/3){
            h = 3 * h + 1;
        }
        while (h >= 1){
            for (int i = h; i < N; i++){
                for (int j = i;j > h && less(a[j], a[j - h]); j -= h){
                    exch(a, j, j - h);
                }
            }
            h = h/3;
        }
    }


//也可以这样写
    public static void sort(Comparable[] a){
        int N = a.length;
        int h = N;
        while (h > 1){
            h = h / 3 + 1;
            for (int i = h; i < N; i++){
                for (int j = i;j > h && less(a[j], a[j - h]); j -= h){
                    exch(a, j, j - h);
                }
            }
        }
    }

步长的选择是希尔排序中的重要部分:

  • 初始步长要小于数组长度。
  • 最终步长要为1,这时就变为了插入排序,确保数组一定会被完全排序。

步长序列不同,希尔排序的效率也不同,但是效率都优于N^2。
 
 

归并排序

将数组分成两半排序,然后将结果归并起来。采用了分治思想。
实现方式有很多,可以采取自顶向下(递归)的方式排序,也可以采取自底向上(非递归)的方式排序。可以使用额外空间来辅助归并,也可以不使用额外空间(局部利用插入排序)来归并。

    //自顶向下
    public static void sortUpTodDown(Comparable[] a){
        aux = new Comparable[a.length];
        sortUpToDown(a, 0, a.length - 1);
    }
    public static void sortUpToDown(Comparable[] a, int lo, int hi){
        if (hi <= lo){
            return;
        }
        int mid = lo + (hi - lo)/2;
        sortUpToDown(a, lo, mid);
        sortUpToDown(a, mid + 1, hi);
        merge(a, lo, mid, hi);
    }

    //自底向上
    public static void sortDownToUp(Comparable[] a){
        int N = a.length;
        aux = new Comparable[N];
        //sz是子数组大小
        for (int sz = 1; sz < N; sz *= 2){
            for (int lo = 0; lo < N - sz; lo += sz * 2){
                merge(a, lo, lo + sz - 1, Math.min(lo + sz * 2 - 1, N -1));
            }
        }
    }

    //归并(使用额外空间)
    //辅助数组
    private static Comparable[] aux;
    public static void merge(Comparable[] a, int lo, int mid, int hi){
        //优化,证明数组已经有序
        if (less(a[mid], a[mid + 1])){
            return;
        }
        int i = lo, j = mid + 1;
        for (int k = lo;k <= hi; k++){
            aux[k] = a[k];
        }
        for (int k = lo; k <= hi; k++){
             if (i > mid){
                 a[k] = aux[j++];
             }
             else if (j > hi){
                 a[k] = aux[i++];
             }
             else if (less(aux[i], aux[j])){
                 a[k] = aux[i++];
             }
             else {
                 a[k] = aux[j++];
             }
        }

    }

    //归并(不使用额外空间,从mid+1元素开始使用插入排序,将第二个数组的元素不断插入到第一个数组中)
    public static void merge2(Comparable[] a, int lo, int mid, int hi){
        if (less(a[mid], a[mid + 1])){
            return;
        }
        for (int i = mid + 1;i <= hi; i++){
            for (int j = i; j > 0 && less(a[j], a[j - 1]); j--){
                exch(a, j, j - 1);
            }
        }
    }

归并排序的效率,与实现方式有一定的关系。
总运行时间为划分时间+归并时间。
两种划分方式,时间复杂度都为N,并且都需要进行logN次归并。
两种归并方式,时间复杂度都为N,但优化后的最好情况下时间复杂度为1。
总时间复杂度为N+NlogN(视为NlogN)

最坏情况下(所有元素逆序),时间复杂度NlogN
平均情况下(随机排列),时间复杂度NlogN(未优化),N~NlogN(优化)
最好情况下(已经有序),时间复杂度NlogN(未优化),N(优化)
 
 

快速排序

快速排序和归并排序时互补的:
归并排序先使子数组(单个元素开始)有序,不断两两归并变为更大的有序子数组,最终使整个数组有序。
快速排序使整个数组进行切分,保证最低程度的有序(以切分点为界,前一部分都小于后一部分),再对切分得到的两个子数组继续切分,使有序性进一步加大。最终当不可再切分时,完全有序。整个过程相当于不断改变元素位置使之始终处在正确的区域,当划定的区域只能容纳一个元素时,每个元素都在正确的位置,整体有序。
都采用了分治的思想。

    public static void sort(Comparable[] a){
        sort3part(a, 0, a.length - 1);
    }

    private static void sort(Comparable[] a, int lo, int hi){
        if (hi <= lo){
            return;
        }
        int index = partition(a, lo, hi);
        sort(a, lo, index - 1);
        sort(a, index + 1, hi);
    }

    //切分点左侧的元素都小于等于切分元素,右侧都大于切分元素
    private static int partition(Comparable[] a, int lo, int hi) {
        Comparable v = a[lo];
        int i = lo, j = hi;
        while (true) {
            //从左向右扫描,直到找到一个大于v的元素
            while (i <= hi && !less(v, a[i])) i++;
            //从右向左扫描,直到找到一个小于等于v的元素
            while (j >= lo && less(v, a[j])) j--;
            if (i >= j) break;
            exch(a, i, j);
        }
        exch(a, lo, j);
        return j;

    }

快速排序的效率与切分点的选择有很大关系
最坏情况下(每次切分后的两子数组之一为空),时间复杂度N^2
平均情况下(随机数据),时间复杂度NlogN
最好情况下(切分点每次都正好将数组对半分),时间复杂度NlogN
 
 

三向切分快速排序

当有大量重复数据时,可以使用三向切分的快速排序提高效率。
维护一个指针lt,使得a[lo..lt-1]中的元素都小于v
维护一个指针gt,使得a[gt+1..hi]中的元素都大于v
维护一个指针i,使得a[lt..i-1]中的元素都等于v,a[i..gt]中的元素未确定

    private static void sort3part(Comparable[] a, int lo, int hi){
        if (hi < lo) return;
        int lt = lo, i = lo + 1, gt = hi;
        Comparable v = a[lo];
        while (i <= gt){
            if (less(a[i], v)) exch(a, lt++, i++);
            else if (less(v, a[i])) exch(a, i, gt--);
            else i++;
        }
        sort3part(a, lo, lt - 1);
        sort3part(a, gt + 1, hi);
    }

当有大量重复数据时,这种方法效率很高。
但是若数据重复率低时,这种方法比标准的快速排序多了许多不必要的交换操作,效率要低不少。

猜你喜欢

转载自blog.csdn.net/qq_31730735/article/details/81273013