排序算法

選擇排序

  • 含義:從第一個值往后開始查找,每次找到最小的值然后與第一個值進行交換,遍歷整個數(shù)據(jù)達到有序。代碼實現(xiàn)如下:
public static void sort(Comparable[] a)
{
    //首先獲取長度
    N = a.length();
    for(int i=0;i<N;i++) {
        min = i;//假設這是最小的下標
        for(int j=i+1;j<N;j++) {
            if(less(a[j],a[min])) {
                min = j;//a[j]對應的值比a[min]小進行交換
            }
        }
        exch(a,min,j);//一次內(nèi)循環(huán)結束交換
    }
}

插入排序

  • 含義:像整理撲克牌一樣,每次一張一張的把牌移入到有序的牌組中,只是代碼中因為有空間需求,所以需要進行位置交換騰換空間,代碼如下:
public static void sort(Comparable[] a) {
    int N = a.length();//獲取數(shù)組長度
    for(int i=1;i<N;i++) {
        //有序數(shù)組不能為空,所以i從1開始
        for (int j=i;j>0 && less(a[j],a[j-1];j--) {
            //二重循環(huán)是從大檢索到最小依次比較排序使得有序排列
            exch(a,j,j-1);//交換
        }
    }
}

希爾排序

  • 含義:插入排序的升級版,步長不在為1,而是設定為一個h可以加快往后排列的小數(shù)回歸到前面的正常位置中,比起插入排序效率更高
public static void sort(Comparable[] a) {
    N = a.length();
    //或者這樣設定步長(算法書上的)
    # int h=1;
    # while(h < N/3) {
    #   h = 3*h + 1;   
    #}
    #下面的更改為h = h/3也可,性能不一樣
    int h = N/2;//設定步長
    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/2;
    }
}

歸并排序:

  • 時間復雜度是NlgN,命題對于長度為N的任意數(shù)組,自頂向下的歸并排序需要1/2
  • 原地歸并方法:需要一個輔助數(shù)組,將原數(shù)組copy到輔助數(shù)組中,然后將輔助數(shù)組的數(shù)據(jù)根據(jù)lo,mid,hi分為兩組依次歸并到原數(shù)組中,具體實現(xiàn)代碼如下,關于值的設定for循環(huán)中的語句含義還是畫一個圖看:
private static void Comparable[] aux;//設定歸并所需的輔助數(shù)組
public static void merge(Comparable[] a, int lo, int mid, int hi) {
    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[j],aux[i])) {
            a[k] = aux[j++];
        }
        else {
            a[k] = aux[i++];
        }
    }
}
public static void sort(Comparable[] sort) {
    aux = new Comparable[a.length];
    sort(a,0,a.length - 1);
}

public static void sort(Comparable[] a,int lo,int hi) {
    if (hi<=lo) return;
    int mid = (lo + hi)/2;
    sort(a,lo,mid);
    sort(a,mid+1,hi);
    merge(a,lo,mid,hi);
}
/**
 * 自頂向上的歸并排序
 *多次遍歷整個數(shù)組,根據(jù)子數(shù)組大小進行兩兩歸并
 * 類似于1+1=2,2+2=4一直到大于N結束為止
 * lo , mid = lo+sz -1, hi = lo +2*sz -1
 */
public static void sortUp(Comparable[] a) {
    int N = a.length;
    aux = new Comparable[a.length];
    for (int sz=1; sz<N; sz = sz + sz) {
        //lo < N-sz 保證mid = lo + sz <N,即是說[mid, hi]還存在著數(shù)據(jù),這個數(shù)據(jù)只是可能沒有sz這么大
        for (int lo=0; lo < N-sz; lo += sz+sz) {
            //進行逐序歸并,利用Math.max防止最大值索引直接越界
            merge(a, lo, lo+sz-1, Math.min(lo+sz+sz-1, N-1));
        }
    }
}

快速排序

  • 含義:采用的是分治的排序算法。快排和歸并排序是互補的算法,歸并需要額外的輔助數(shù)組,快速排序不需要。實現(xiàn)的是兩個子數(shù)組有序時整個數(shù)組也就有序了。舉例來說如下:首先從數(shù)組中選定一個值,假如是第一個值,然后遍歷整個數(shù)組,將比這個值小的數(shù)據(jù)放在這個數(shù)的左邊,比這個大的放在數(shù)據(jù)右邊,從兩頭往中間縮進,所以需要的時間會小很多,然后再進行遞歸遍歷實現(xiàn)最后以1位間隔的數(shù)據(jù)段,自然整個數(shù)組也就有序了。
  • 代碼如下:
public static void sort(Comparable[] a) {
int N = a.length;
sort(a, 0, N-1);
}
private static void sort(Comparable[] a, int lo, int hi) {
if (hi < lo) return;//遞歸結束條件返回
int j = partition(a, lo, hi);
sort(a, lo, j-1);//左邊數(shù)組快排
sort(a, j+1, hi);//右邊數(shù)組快排
}
//以下這個函數(shù)是實現(xiàn)快排的重要代碼
private static int partition(Comparable[] a, int lo, int hi) {
int i = lo, j = hi + 1;
while (true) {
    while (less(a[i++],a[lo])) {
    //控制越界
        if (i == hi) {
            break;
        }
    }
    while (less(a[lo],a[j--])) {
        if (j == lo) {
            break;
        }
    }
    if (i >= j) break;
    exch(a, i, j);
}
exch(a, lo, j);
return j;
}
//快排實現(xiàn)代碼2感覺比上面那一種簡單
private static int partitionBack(Comparable[] a, int lo, int hi) {
        Comparable key = a[lo];
        while(lo < hi) {
            while (lo < hi && less(key, a[hi])) hi--;
            a[lo] = a[hi];
            while (lo < hi && less(a[lo], key)) lo++;
            a[hi] = a[lo];
        }
        a[hi] = key;
        return hi;
    }

堆排序

  • 含義:采用堆的形式(大根堆:父節(jié)點都比子節(jié)點大,小根堆:父節(jié)點都比子節(jié)點?。?,首先將一堆數(shù)據(jù)排列成為大根堆,然后從根節(jié)點取出最大值,一次放入到隊列的最后,就可以變成一個有序的從小到大排序的數(shù)據(jù),其時間復雜度為NlgN。
  • 父節(jié)點和子節(jié)點滿足以下關系:父節(jié)點序號*2+1 或者 父節(jié)點序號*2 + 2 = 子節(jié)點序號,父節(jié)點序號 = 子節(jié)點序號-1/2
  • 算法如下:
private static void sink(Comparable[] a, int k, int N) {
    //下沉算法
    while (2*k+1 < N) {
        int j = 2*k+1;//找尋子節(jié)點
        //比較兩個子節(jié)點找出最大的
        if (j<N && less(a[j], a[j+1])) {
            j++;
        }
        //比較父節(jié)點和最大的子節(jié)點
        if(!less(a[k], a[j])) {
            //滿足堆特性,結束循環(huán)
            break;
        }
        //進行交換
        exch(a,k,j);
        //繼續(xù)下層遍歷
        k = j;
    }
}

//實現(xiàn)堆有序的過程
public static void sort(Comparable[] a) {
    int N = a.length - 1;//數(shù)組和實際下標有一個1的差距
    //實現(xiàn)對有序
    for (int k = N/2; k>=1; k--) {
        sink(a, k, N);
    }
    //進行堆排序,每次將根節(jié)點挪到后面在進行堆排序循環(huán)
    while (N >1 ) {
        exch(a,1,N--);
        sink(a,1,N);
    }
}

冒泡排序

  • 含義:每一輪從前往后的遍歷,較大數(shù)據(jù)后排,就像魚吐泡泡一樣越來越大,輪次結束后數(shù)組整體有序
  • 源代碼如下:
package com.zhou.sort;

import com.zhou.prepare.StdOut;

/**
 * Created by 強 on 2018/11/21.
 * 冒泡排序:每次進行兩個數(shù)字的比較,較大者放到后面,從前往后依次進行直到最后一個是本輪次的最大的數(shù),然后繼續(xù)下一輪直到整個數(shù)組有序
 */
public class Bubble {
    public static void main(String[] args) {
        Integer[] a = {5, 3, 2, 10, 1, 15, 4,90};
        bubble_sort(a);
        show(a);
        System.out.println(isSorted(a));
    }

    private static void sort(Comparable[] a) {
        //外層循環(huán)控制輪次
        for (int i = 0; i < a.length; i++) {
            //內(nèi)存循環(huán)控制比較
            for (int j = 0; j < a.length - i - 1; j++) {
                if (less(a[j+1], a[j])) exch(a, j, j+1);
            }
        }
    }

    /**
     * 將雙層for循環(huán)進化為一個while循環(huán)加一個for循環(huán),此時若數(shù)組處于有序狀態(tài)可以大大減小排序時間
     * 比如數(shù)組本身有序,那么時間復雜度進化為O(n),逆序狀態(tài)下與原始冒泡排序等同,平均復雜度和逆序等同
     * @param a
     */
    private static void bubble_sort(Comparable[] a) {
        int pos = a.length - 1;
        while (pos != 0) {
            int bound = pos;
            pos = 0;//用于標記是否有交換記錄,若有,則記錄"下一趟要處理的最后一個位置"
            for (int j = 0; j < bound; j++) {
                if (less(a[j+1], a[j])) {
                    exch(a, j, j+1);
                    pos = j;
                }
            }
        }
    }

    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++) {
            StdOut.print(a[i] + " ");
        }
        StdOut.println();
    }

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

基數(shù)排序(桶排序)

  • 含義:從最基本的認知出發(fā)的排序,兩個數(shù)比較大小首先是看位數(shù)是否相同,如果不同那么位數(shù)多的肯定比位數(shù)少的大,位數(shù)相同則逐個位置進行比較大小進行排序。這個每一位比較就被稱為基數(shù)(個位/十位/百位等),這個排序過程又可以分為MSD(高位優(yōu)先排序,從左到右)和LSD(低位優(yōu)先排序,從右到左)和字符串的排序倒是有異曲同工之處。

  • 此處著眼于LSD,具體實現(xiàn)的過程如下:

  • 首先聲明10個桶,每個桶代表的數(shù)字0-9,也就是對應的基數(shù),然后對于每一個數(shù)字按照從低位優(yōu)先(個位開始)進行取余,加如對應的位數(shù)字為5,那么就放入數(shù)字為5的桶中,依次將數(shù)組中所有數(shù)據(jù)都放入桶中。

  • 之后再從桶0開始遍歷,將桶中所有數(shù)據(jù)依次取出(同一個桶中的數(shù)據(jù)按照先進先出的特性)放入到原數(shù)組中

  • 然后將基數(shù)擴大十倍(個位變百位,百位變千位,依次類推),重復上述過程直到最后循環(huán)結束

  • 舉例來說,一組數(shù)據(jù)5、2、9、7、18、17、52

  • 如下圖中所示,按照個位數(shù)字大小依次放入桶中,然后再 將桶中的數(shù)據(jù)串起來輸出得到排序后的結果2、52、5、7、17、18、9,然后以此順序進行十位的桶排序

  • 如下圖第二張圖中所示,重復操作即可得到最后有序的數(shù)組完成此次排序的結果

  • 在代碼實現(xiàn)過程中用一個二維數(shù)組實現(xiàn),行為10表示10個桶,列表示的是桶中藥存儲的數(shù)據(jù),長度為數(shù)組長度,然后聲明一個長度為10的一維數(shù)組用來表示每個桶中有多少數(shù)據(jù)


    第一次桶排序

    第二次桶排序
  • 源代碼實現(xiàn)如下所示:

package com.zhou.sort;

/**
 * Created by 強 on 2019/4/6.
 */
public class RadixSort {
    public static void main(String[] args) {
        int[] num = new int[50];
        for (int i=0; i<num.length; i++) {
            num[i] = (int)(Math.random()*1000);
        }
        radixSort(num, 1000);
        System.out.println("the result is order: " + isSorted(num));
        for (int i=0; i<num.length; i++) {
            System.out.print(num[i]+ " ");
        }
    }

    //d表示的是數(shù)組中最大位的位數(shù)+1位,舉例來說加如排序的都是1-99兩位數(shù),那么d就是100,如果是1-999那么d就是1000
    private static void radixSort(int[] array, int d) {
        int n = 1;//代表位數(shù)對應的數(shù):1、10、100
        int length = array.length;
        int[][] bucket = new int[10][length];//排序桶用于保存每次排序后的結果,這一位上排序結果相同的數(shù)字放在同一個桶里
        int[] order = new int[10];//用于保存每個桶里有多少個數(shù)字,一共有10個桶
        while (n<d) {
            for (int num:array) {
                //將數(shù)組array每個數(shù)字放在相應的桶里
                int digit = (num/n)%10;
                bucket[digit][order[digit]]=num;
                order[digit]++;
            }
            int k=0;//回寫到原數(shù)組時,原數(shù)組下標索引
            for (int i=0; i<10; i++) {
                //將前一個循環(huán)生成的桶里的數(shù)據(jù)覆蓋到原數(shù)組中用于保存這一位的排序結果
                if(order[i]!=0) {
                    //這個桶里有數(shù)據(jù),從上到下排序并將數(shù)據(jù)保存到原數(shù)組中
                    for (int j=0; j<order[i]; j++) {
                        array[k] = bucket[i][j];
                        k++;
                    }
                }
                order[i]=0;//將桶里計數(shù)器清零,用于下一次位排序
            }
            n*=10;
        }
    }

    private static boolean isSorted(int[] a) {
        for (int i=1; i<a.length; i++) {
            if (a[i]<a[i-1]) {
                return false;
            }
        }
        return true;
    }
}

排序算法總結

  • 所需輔助空間最多:歸并排序
  • 所需輔助空間最少:堆排序
  • 平均速度最快:快速排序
  • 不穩(wěn)定性:快速排序、希爾排序、堆排序(不穩(wěn)定性指的是相同的兩個數(shù)在排序前后的相對位置是否改變)
  • 平均時間復雜度為O(nlogn)的方法有:快速排序、堆排序和歸并排序,快排最好
  • 平均時間復雜度為O(n2)的方法有:直接插入排序、冒泡排序、簡單選擇排序,其中直接插入排序最好,尤其是對那些關鍵字近似有序的記錄序列
  • 平均時間復雜度為O(n)的只有基數(shù)排序


    排序算法比較表

排序完整代碼鏈接

https://github.com/zhouwaiqiang/JavaLearning/tree/master/algorithm/src/com/zhou/sort

參考資料

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容