十種排序方法

什麼是算法的穩定性?

簡單的說就是一組數通過某個排序算法後仍然能保持他們在排序以前的相對次序就說這個排序方法是穩定的, 好比說,a1,a2,a3,a4四個數, 其中a2=a3,若是通過排序算法後的結果是 a1,a3,a2,a4咱們就說這個算法是非穩定的,若是仍是原來的順序a1,a2,a3,a4,咱們就會這個算法是穩定的html

1.選擇排序

選擇排序,顧名思義,在循環比較的過程當中確定存在着選擇的操做, 算法的思路就是從第一個數開始選擇,而後跟他後面的數挨個比較,只要後面的數比它還小,就交換二者的位置,而後再從第二個數開始重複這個過程,最終獲得從小到大的有序數組java

算法實現:算法

public static void select(int [] arr){
    // 選取多少個標記爲最小的數,控制循環的次數
    for (int i=0;i<arr.length-1;i++){
    
        int minIndex = i;
        
        // 把當前遍歷的數和它後面的數依次比較, 並記錄下最小的數的下標
         for (int j=i+1;j<arr.length;j++){
             // 讓標記爲最小的數字,依次和它後面的數字比較,一旦後面有更小的,記錄下標
             if (arr[minIndex]>arr[j]){
                 // 記錄的是下標,而不是着急直接交換值,由於後面可能還有更小的
                 minIndex=j;
             }
         }
        // 當前最小的數字下標不是一開始咱們標記出來的那個,交換位置
        if (minIndex!=i){
            int temp=arr[minIndex];
            arr[minIndex]=arr[i];
            arr[i]=temp;
        }
    }
}
}

時間複雜度: n + n-1 + n-2 + .. + 2 + 1 = n*(n+1)/2 = O(n^2)shell

穩定性: 好比: 5 8 5 2 7 通過一輪排序後的順序是2 8 5 5 7, 原來兩個5的先後順序亂了,所以它不穩定數組

推薦的使用場景: n較小時安全

輔助存儲: 就一個數組,結果是O(1)數據結構

2.插入排序

見名知意,在排序的過程當中會存在插入的狀況,依然是從小到大排序 算法的思路: 選取第二個位置爲默認的標準位置,查看當前這個標準位置以前有沒有比它還大的元素,若是有的話就經過插入的方式,交換二者的位置,怎麼插入呢? 就是找一箇中間變量記錄當前這個標準位置的值,而後讓這個標準位置前面的元素往前移動一個位置,這樣如今的標準位置被新移動過來的元素給佔了,可是前面空出來一個位置, 因而將這個存放標準值的中間元素賦值給這個空出來的位置,完成排序性能

代碼實現:ui

/**
 * 思路: 從數組的第二個位置開始,選定爲標準位置,這樣開始的話,能夠保證,從標準位置開始往前所有是有序的
 * @param arr
 */
private static void insetSort(int[] arr) {
    int temp;
    // 從第二個開始遍歷index=1 , 一共length 個數,一直循環到,最後一個數參加比較, 就是 1  <->  length 次
    for (int i=1;i<arr.length;i++){
        // 判斷大小,要是如今的比上一個小的話,準備遍歷當前位置之前的有序數組
        if (arr[i] < arr[i-1]){
            // 存放當前位置的值
             temp=arr[i];
            int j;
            // 循環遍歷當前位置及之前的位置的有序數組,只要是從當前位置開始,前面的數比當前位置的數大,就把這個大的數替插入到當前的位置
            // 隨着j的減小,實際上每次循環都是前一個插到後一個的位置上
            for (j=i-1;j>=0&&temp<arr[j];j--){
                arr[j+1]=arr[j];
            }
            // 直到找出一個數, 不比原來存儲的那個當前的位置的大,就把存起來的數,插到這個數的前面
            arr[j+1]=temp;
        }
    }
}

時間複雜度:.net

  • 最好的狀況就是: 數組原本就是有序的, 這樣算法從第2個位置開始循環n-1次, 時間複雜度就是 n
  • 最壞的狀況: 外層 n-1次, 內存分別是 1,2,3,4...n-2 ==> (n-2)(n-1)/2 = O(n^2)
  • 平均時間複雜度: O(n^2)

穩定性: 從第2個位置開始遍歷,標準位以前數組一直是有序的,因此它確定穩定

輔助存儲: O(1)

推薦的使用場景: n大部分有序時

3.冒泡排序

最熟悉的排序方式

  • 從零位的數開始,比較總長度-1大輪
  • 每個大輪比較 總長度-1-當前元素的index 小輪

代碼實現:

private static void sort(int[] ints) {
    System.out.println("length == "+ints.length);
    // 控制比較多少輪 0- length-1  一共length個數,最壞比較lenth-1次
    for (int i=0;i<ints.length-1;i++){
        // 每次都從第一個開始比較,每次比較的時候,最多比較到上次比較的移動的最新的下標的位置,就是  length-0-i
        for (int j=0;j<ints.length-1-i;j++){
            int temp;
            if (ints[j] > ints[j+1]) {
                temp=ints[j];
                ints[j]=ints[j+1];
                 ints[j+1]=temp;
            }
        }
    }
}

時間複雜度:

  • 作好的狀況下: 數組自己就有序,執行的就是最外圈的for循環中的n次
  • 最壞的狀況: (n-1) + (n-2) + (n-3) +...+ 1 = n(n-1)/2 當n趨近於無限大時, 時間發雜度 趨近於n^2

穩定性: 穩定

輔助存儲空間: O(1)

推薦的使用場景: n越小越好

4.歸併排序

算法的思路: 先經過遞歸將一個完整的大數組[5,8,3,6]拆分紅小數組, 通過遞歸做用,最終最小的數組就是[5],[8],[3],[6]

遞歸到最底層後就會有彈棧的操做,經過這時建立一個臨時數組,將這些小數組中的數排好序放到這個臨時數組中,再用臨時數組中的數替換待排序數組中的內容,實現部分排序, 重複這個過程

/**
 *  爲何須要low height 這種參數,而不是經過 arr數組計算出來呢?
 *          --> 長個心,每當使用遞歸的時候,關於數組的這種信息寫在參數位置上
 * @param arr  須要排序的數組
 * @param low  從哪裏開始
 * @param high 排到哪裏結束
 */
public static void mergeSort(int[] arr, int low, int high) {
    int middle = (high + low) / 2;
    if (low < high) {

        // 處理左邊;
        mergeSort(arr, low, middle);

        // 處理右邊
        mergeSort(arr, middle + 1, high);

        // 歸併
        merge(arr, low, middle, high);
    }
}

/**
 * @param arr
 * @param low
 * @param middle
 * @param high
 */
public static void merge(int[] arr, int low, int middle, int high) {
    // 臨時數組,存儲歸併後的數組
    int[] temp = new int[high - low + 1];

    // 第一個數組開始的下標
    int i = low;

    // 第二個數組開始遍歷的下標
    int j = middle + 1;

    // 記錄臨時數組的下標
    int index = 0;

    // 遍歷兩個數組歸併
    while (i <= middle && j <= high) {
        if (arr[i] < arr[j]) {
            temp[index] = arr[i];
            i++;

        } else {
            // todo 在這裏放個計數器++ , 能夠計算得出 反序對的個數 (這樣的好處就是時間的複雜度是  nlogn)
            temp[index] = arr[j];
            j++;
        }
        index++;
    }

    while (j <= high) {
        temp[index] = arr[j];
        j++;
        index++;
    }
    while (i <= middle) {
        temp[index] = arr[i];
        i++;
        index++;
    }
    // 把臨時入數組中的數據從新存原數組
    for (int x = 0; x < temp.length; x++) {
        System.out.println("temp[x]== " + temp[x]);
        arr[x + low] = temp[x];
    }
}

按上面說的[5,8,3,6]來講,通過遞歸他們會被分紅這樣

---------------壓棧--------------------
 左             右
[5,8]         [3,6]
[5] [8]      [3] [6]
------------下面的彈棧-----------------

[5]和[8]歸併,5<8  獲得結果:   [5,8] 

[3]和[6]歸併, 3<6 獲得結果    [3,6]

因而通過兩輪歸併就獲得告終果
[5,8]         [3,6]


繼續歸併: 建立一個臨時數組 tmp[]
5>3  tmp[0]=3
5<6  tmp[1]=5
8>6  tmp[2]=6

tmp[3]=8

而後讓tmp覆蓋原數組獲得最終結果

推薦使用的場景: n越大越好

時間複雜度: 最好,平均,最壞都是 O(nlogn) (這是基於比較的排序算法所能達到的最高境界)

穩定性能: 穩定

空間複雜度: 每兩個有序序列的歸併都須要一個臨時數組來輔助,所以是 O(N)

5.快速排序

是分支思想的體現, 算法的思路就是每次都選出一個標準值,目標是通過處理,讓當前數組中,標準值左邊的數都小於標準值, 標準值右邊的數都大於標準值, 重複遞歸下去,最終就能獲得有序數組

實現:

// 快速排序
public static void quickSort(int[] arr, int start, int end) {
    // 保證遞歸安全的進行
    if (start < end) {
        // 1. 找一箇中間變量,記錄標準值,通常是數組的第一個,以這個標準的值爲中心,把數組分紅兩部分
        int strand = arr[start];

        // 2. 記錄最小的值和最大的值的下標
        int low = start;
        int high = end;

        // 3. 循環比較,只要個人最開始的low下標,小於最大值的下標 就不停的比較
        while (low < high) {
            System.out.println("high== " + high);
            // 從右邊開始,若是右邊的數字比標準值大,把下標往前動
            while (low < high && strand <= arr[high]) {
                high--;
            }
            // 右邊的最high的數字比 標準值小, 把當前的high位置的數字賦值給最low位置的數字
            arr[low] = arr[high];

            // 接着從作low 的位置的開始和標準值比較, 若是如今的low位置的數組比標準值小,下標日後動
            while (low < high && arr[low] <= strand) {
                low++;
            }
            // 若是low位置的數字的比 標準值大,把當前的low位置的數字,賦值給high位置的數字
            arr[high] = arr[low];

        }
        // 把標準位置的數,給low位置的數
        arr[low] = strand;

        // 開始遞歸,分開兩個部分遞歸
        // 右部分
        quickSort(arr, start, low);
        // 左部分
        quickSort(arr, low + 1, end);
    }
}

推薦使用場景: n越大越好

時間複雜度:

  • 最壞: 其實你們能夠看到,上面的有三個while,可是每次工做的最多就兩個,若是真的就那麼不巧,全部的數兩兩換值,那麼最壞的結果和冒泡同樣 O(n^2)
  • 最好和平均都是: O(nlogn)

穩定性: 快排是不穩定的

6.希爾排序

希爾排序其實能夠理解成一種帶步長的排序方式, 上面剛說了插入排序的實現方式,上面說咱們默認從數組的第二個位置開始算,實際上就是說步長是1,下標的移動每次都是1

對於希爾排序來講,它默認的步長是 arr.length/2 , 每次步長都減小一半, 最終的步長也會是1

代碼實現:

/**
 *  希爾排序,在插入排序的基礎上,添加了步長 ,
 *  // todo 只要在本步長範圍內,這些數字爲組成一個組進行 插入排序
 *  初始  步長=length/2
 *  後來: 步長= 步長/2
 *  直到最後: 步長=1; 正宗的插入排序
 * @param ints
 */
private static void shellSort(int[] ints) {
    // 記錄當前的步長
    int step=ints.length/2;

    // 步長==》控制遍歷幾輪, 而且每次步長都是上一次的一半大小
    for (int i=step;i>0;i/=2){

        // 遍歷當前步長下面的所有組,這裏的j1, 就至關於插入排序中第一次開始的位置1前面的0下標
        for (int j1=i;j1<ints.length;j1++){
            
            // 遍歷本組中所有元素 == > 從第二個位置開始遍歷
            // x 就至關於插入排序中第一次開始的位置1
            for(int x=j1+i;x<=ints.length-1;x+=i){

                // 從當前組的第二個元素開始,一旦發現它前面的元素比他小,
                // 插入排序, 1. 把當前的元素存起來  2. 循環它前面的元素往前移 3. 把當前元素插入到合適的位置
                if(ints[x]<ints[x-i]){
                    int temp=ints[x];
                    int j ;
                    for(j=x-i;j>=0&&ints[j]>temp;j=j-i)
                    {
                        ints[j+i]=ints[j];
                    }
                    ints[j+i]=temp;
                }
            }
        }
}

}

空間複雜度: 和插入排序同樣都是O(1)

穩定性: 希爾排序因爲步長的緣由,而不向插入排序,一經開始標準位置前的數組即刻有序, 因此希爾排序是不穩定的

希爾排序的性能沒法準確量化,跟輸入的數據有很大關係在實際應用中也不會用它,由於十分不穩定,雖然比傳統的插入排序快,但比快速排序等慢

其時間複雜度介於O(nlogn) 到 O(n^2) 之間

7.堆排序

堆排序是藉助了堆這種數據結構完成的排序方式,堆有大頂堆和小頂堆, 將數組轉換成大頂堆而後進行排序的會結果是數組將從小到大排序,小叮噹則相反

什麼是堆呢? 堆實際上是能夠當作一顆徹底二叉樹的數組對象, 那什麼是徹底二叉樹呢? 好比說, 這顆數的深度是h,那麼除了h層外, 其餘的1~h1層的節點都達到了最大的數量

算法的實現思路: 經過遞歸,將數組當作一個堆,從最後一個非葉子節點開始,將這個節點轉換成大頂堆, 什麼是大頂堆呢? 就是根節點老是大於它的兩個子節點, 重複這個過程一直遞歸到堆的根節點(此時根節點是最大值),此時整個堆爲大頂堆, 而後交換根節點和最後一個葉子節點的位置,將最大值保存起來

例: 假設待排序序列是a[] = {7, 1, 6, 5, 3, 2, 4},而且按大根堆方式完成排序

  • 第一步(構造初始堆):

堆

  • 第二步(首尾交換,斷尾重構):

堆2

點擊查看例子參考連接:

代碼實現:

public static void sort(int[] ints) {
        // 開始位置是最後一個非葉子節點
        int start = ints.length / 2-1;

        // 調整成大頂堆
        for (int i = start; i >= 0; i--) {
            maxHeap(ints, ints.length, i);
        }

        // 調整第一個和最後一個數字, 在把剩下的轉換爲大定堆,  j--實現了,再也不調整本輪選出的最大的數
        for (int j = ints.length - 1; j > 0; j--) {
            int temp = ints[0];
            ints[0] = ints[j];
            ints[j] = temp;

            maxHeap(ints, j, 0);
        }


    }

    /**
     * 轉換爲大頂堆, 其實就是比較根節點和兩個子節點的大小,調換他們的順序使得根節點的值大於它的兩個子節點
     *
     * @param arr
     * @param size
     * @param index  從哪一個節點開始調整  (一開始轉換爲大頂堆時,使用的是最後一個非夜之節點, 可是轉換完成以後,使用的就是0,從根節點開始調整)
     */
    public static void maxHeap(int[] arr, int size, int index) {
        // 當前節點的左子節點
        int leftNode = 2 * index + 1;
        // 當前節點的右子節點
        int rightNode = 2 * index + 2;
        // 找出 當前節點和左右兩個節點誰最大
        int max = index;
        if (leftNode < size && arr[leftNode] > arr[max]) {
            max = leftNode;
        }
        if (rightNode < size && arr[rightNode] > arr[max]) {
            max = rightNode;
        }

        // 交換位置
        if (max != index) {
            int temp = arr[index];
            arr[index] = arr[max];
            arr[max] = temp;
            // 交換位置後,可能破壞以前的平衡(跟節點比左右的節點小),遞歸
            // 有可能會破壞以max爲定點的子樹的平衡
            maxHeap(arr, size, max);
        }
    }

推薦的使用場景: n越大越好

時間複雜度: 堆排序的效率與快排、歸併相同,都達到了基於比較的排序算法效率的峯值(時間複雜度爲O(nlogn))

空間複雜度: O(1)

穩定性: 不穩定

基數排序

圖解基數排序 圖片來源: 點擊進入連接

算法思路:

看上圖中的綠色部分, 假設咱們有下標從0-9,一共10個桶

第一排是給咱們排序的一組數

咱們分別對取出第一排數的個位數,放入到對應下標中的桶中,再依次取出,就獲得了第三行的結果, 再取出三行的十位數,放入到桶中,再取出,就獲得最後一行的結果

// 基數排序
// 建立10個數組,索引從0-9
// 第一次按照個位排序
// 第二次按照十位排序
// 第三次按照百位排序
// 排序的次數,就是數組中最大的數的位數
public static void radixSort(int[] arr){

    int max = Integer.MIN_VALUE;

    // 循環找到最大的數,控制比較的次數
    for (int i : arr) {
        if (i>max){
            max=i;
        }
    }

    System.out.println(" 最大值: "+max);
    // 求最大數字的位數,獲取須要比較的輪數
    int maxLength = (max+"").length();
    System.out.println(" 最大串的長度: "+maxLength);

    // 建立應用建立臨時數據的數組, 整個大數組中存放着10個小數組, 這10個小數組中真正存放的着元素
    int [][] temp = new int[10][arr.length]; // 10個 長度爲arr.length長度的數組
    // todo 用於記錄在temp中相應的數組中存放的數字的數量
    int [] counts = new int[10];

    // 還須要添加另外一個變量n 由於咱們每輪的排序是從的1 - 10 - 100 - ... 開始求莫排序
    for(int i=0,n=1;i<maxLength;i++,n*=10){
        // 計算每個數字的餘數,遍歷數組,將符合要求的放到指定的數組中
        for (int j=0;j<arr.length;j++){
            int x = arr[j]/n%10;
            // 把當前遍歷到的數據放入到指定位置的二維數組中
            temp[x][counts[x]] = arr[j];
            // 更新二維數組中新更改的數組後的 新長度
            counts[x]++;
        }

        int index =0;
        // 把存放進去的數據從新取出來
        for (int y=0;y<counts.length;y++){
            // 記錄數量的那個數組的長度不爲零,咱們才區取數據
            if (counts[y]!=0){
                // 循環取出元素
                for (int z =0;z<counts[y];z++){
                    // 取出
                    arr[index++] = temp[y][z];
                }
            // 把數量置爲0
                counts[y]=0;
            }
        }
    }

}

穩定性: 穩定

時間複雜度: 平均、最好、最壞都爲O(k*n),其中k爲常數,n爲元素個數

空間複雜度: O(n+k)

桶排序

算法思路: 相對比較好想, 給咱們一組數,咱們在選出這組數中最大值和數組的length的長度,選最大的值當成新數組的長度,而後遍歷舊的數組,將舊數組中的值放入到新數組中index=舊數組中的值的位置

而後一次遍歷舊數組中的值,就能獲得最終的結果

代碼實現:

private static void sort(int[] ints) {
    int max = Integer.MIN_VALUE;

    for (int anInt : ints) {
        if (anInt>max)
            max=anInt;
    }
    int maxLength = ints.length-max >0 ? ints.length:max;

    int[] result = new int[maxLength];

    for (int i=0;i<ints.length;i++){
        result[ints[i]] +=1 ;
    }

    for(int i=0 ,index = 0;i<result.length;i++){
        if (result[i]!=0){
            for (int j=result[i];j>0;j--){
              ints[index++] = i;
            }
        }
    }
}

時間複雜度:

  • 平均時間複雜度:O(n + k)
  • 最佳時間複雜度:O(n + k)
  • 最差時間複雜度:O(n^2)

空間複雜度:O(n * k)

穩定性:穩定

典型的用空間換時間的算法

計數排序

算法思路: 根據待排序集合中最大元素和最小元素的差值範圍,申請額外空間;
遍歷待排序集合,將每個元素出現的次數記錄到元素值對應的額外空間內;
對額外空間內數據進行計算,得出每個元素的正確位置;
將待排序集合每個元素移動到計算得出的正確位置上。

代碼實現:

public static int[] sort(int[] A) {
    //一:求取最大值和最小值,計算中間數組的長度:中間數組是用來記錄原始數據中每一個值出現的頻率
    int max = A[0], min = A[0];
    for (int i : A) {
        if (i > max) {
            max = i;
        }
        if (i < min) {
            min = i;
        }
    }

    //二:有了最大值和最小值可以肯定中間數組的長度
    //存儲5-0+1 = 6
    int[] pxA = new int[max - min + 1];

    //三.循環遍歷舊數組計數排序: 就是統計原始數組值出現的頻率到中間數組B中
    for (int i : A) {
        pxA[i - min] += 1;//數的位置 上+1
    }

    //四.遍歷輸出
    //建立最終數組,就是返回的數組,和原始數組長度相等,可是排序完成的
    int[] result = new int[A.length];
    int index = 0;//記錄最終數組的下標

    //先循環每個元素  在計數排序器的下標中
    for (int i = 0; i < pxA.length; i++) {
        //循環出現的次數
        for (int j = 0; j < pxA[i]; j++) {//pxA[i]:這個數出現的頻率
            result[index++] = i + min;//原來原來減小了min如今加上min,值就變成了原來的值
        }
    }
    return result;
}

也是典型的用空間換時間的算法

時間複雜度: O(n+k)

空間複雜度: O(n+k)

穩定性: 穩定

相關文章
相關標籤/搜索