八大排序圖解算法

其實八大排序如果弄清楚它們的原理並不難,雖然裏面有幾種排序寫起來也很麻煩。
但是最難的往往就是,我們會把它們相互混淆,我給每個排序畫了一張動圖,看圖記憶就好很多了。

每種排序都有相對應的解釋和圖,大家可以看完解釋和圖然後按照自己的思路去寫代碼。(當然也提供了測試好的代碼)

末尾提供了測試代碼,你只需要改動一行代碼,就可以測試你的排序是否正確,如果錯誤會打印出原本序列你的排序正確的排序,以幫助你更好的排錯。


穩定性:如果一組待排序的數字中,有兩個相同的數字。在完成排序後,這兩個數字的相對位置不變,即該排序是穩定排序。

不穩定的排序算法:堆排序、快速排序、希爾排序、直接選擇排序.

0(nlogn) 快速排序、堆排序、歸併排序,快速排序最好

初始順序對排序沒有影響的是 堆排序

在這裏插入圖片描述


一、冒泡排序

平均時間複雜度 最好時間複雜度 最壞時間複雜度 空間複雜度 穩定性 類別
O(n2) O(n) O(n2) O(1) 穩定 交換排序

1-1:冒泡排序的解釋

循環遍歷 0-length-1 的元素,找到最合適每個位置的元素。
把位置 i 的元素和i後面的的每個元素對比,找到最小的元素放在 i 這個位置。(找最大還是最小取決你怎麼排序)
下面是圖示:

在這裏插入圖片描述

1-2:冒泡排序代碼

public void bubbleSort(int[] arr){
    int tmp;
    for (int i = 0;i < arr.length - 1; i++){
        for (int j = i+1; j < arr.length; j++){
            if (arr[i] > arr[j]){
                tmp = arr[j];
                arr[j] = arr[i];
                arr[i] = tmp;
            }
        }
    }
}



二、快速排序

平均時間複雜度 最好時間複雜度 最壞時間複雜度 空間複雜度 穩定性 類別
O(nlog2n) O(nlog2n) O(n2) O(nlog2n) 不穩定 交換排序

2-1:快速排序的解釋

每次選擇一個數作爲標準,然後比它大的放在右邊,比它小的放在左邊。
通過上面的操作把數組分成了兩份,然後每份繼續重複這樣的操作,直到排序好。

注:
1、爲了方便,這個標準數,我們每次取第一個數
2、當左邊長度是1的時候就是排序好了。(同理右邊也是)

在這裏插入圖片描述


2-2:快速排序代碼

public static void  quickSort(int[] arr,int left, int right){
    int tmpLeft = left;
    int tmpRight = right;
    int cur = arr[left];
    while (left < right){
        while (arr[right] >= cur && right > left) {
            right--;
        }
        arr[left] = arr[right];
        while (arr[left] < cur && right > left) {
            left ++;
        }
        arr[right] = arr[left];
    }
    arr[left] = cur;
    if (left - tmpLeft > 1){
        quickSort(arr,tmpLeft,left-1);
    }
    if (tmpRight - left > 0){
        quickSort(arr,left+1,tmpRight);
    }
}



三、直接插入排序

平均時間複雜度 最好時間複雜度 最壞時間複雜度 空間複雜度 穩定性 類別
O(n2) O(n) O(n2) O(1) 穩定 插入排序

3-1:直接插入排序的解釋

我們從 1 開始遍歷每一個元素(i),然後把元素 i 插入到 0-i 裏面最合適的位置,這樣就排好序了。

在這裏插入圖片描述


3-2:直接插入排序的代碼

public void insertSort(int[] arr){
    int tmp,j;
    for (int i = 1;i < arr.length; i++){
        if (arr[i] < arr[i-1]){
            tmp = arr[i];
            for (j = i;j > 0; j--){
                if (tmp < arr[j-1]){
                    arr[j] = arr[j-1];
                }else {
                    break;
                }
            }
            arr[j] = tmp;
        }
    }
}



四、希爾排序

4-1:希爾排序解釋

平均時間複雜度 最好時間複雜度 最壞時間複雜度 空間複雜度 穩定性 類別
O(n1.3) O(n) O(n2) O(1) 不穩定 交換排序

希爾排序其實是直接插入排序的一個改進。 直接插入排序每次移動的步長都是 1 ,而希爾排序的步長從 size 開始 直到 1,這樣做的使得 大的數字比較靠後,小的數字比較靠前。 當進行步長爲 1 的直接插入排序的時候,就不會出現每次移動很多的情況。

先將整個待排序列分割成若干個子序列,對每個子序列進行直接插入排序,等到整個序列基本有序的時候,在進行一次整體的直接插入排序(也就是 步長爲 1)。

在這裏插入圖片描述

public void shellInsert(int[] arr, int step){
    int tmp;
    int m;
    for (int i = 0; i < step; i++){
        for (int n = i + step;n < arr.length; n += step){
            if (arr[n] < arr[n - step]){
                tmp = arr[n];
                for (m = n-step;m >= 0; m -= step){
                    if (tmp < arr[m]){
                        arr[m + step] = arr[m];
                    }else {
                        break;
                    }
                }
                arr[m+step] = tmp;
            }
        }
    }
}

public void shellSort(int[] arr){
    int step = arr.length / 2;
//        int step = 1;
    while (step >= 1){
        shellInsert(arr, step);
        step = step / 2;
    }
}



五、直接選擇排序

5-1:直接選擇排序解釋

平均時間複雜度 最好時間複雜度 最壞時間複雜度 空間複雜度 穩定性 類別
O(n2) O(n2) O(n2) O(1) 不穩定 選擇排序

每次從i - length-1中選擇出 一個最小(最大)的數字,然後和 i 進行交換。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-i4WCqdFO-1590475243176)(在這裏插入圖片描述

5-2:直接選擇排序代碼

public void  selectSort(int[] arr){
    int min_i,tmp;
    for (int i = 0;i < arr.length - 1; i++){
        min_i = i;
        for (int j = i+1;j < arr.length; j++){
            if (arr[min_i] > arr[j]){
                min_i = j;
            }
        }
        tmp = arr[i];
        arr[i] = arr[min_i];
        arr[min_i] = tmp;
    }
}



六、堆排序

6-1:堆排序解釋

平均時間複雜度 最好時間複雜度 最壞時間複雜度 空間複雜度 穩定性 類別
O(nlog2n) O(nlog2n) O(nlog2n) O(1) 不穩定 選擇排序

把待排序列變成最大堆(最小堆),然後取出對頂元素。重複以上操作,直到排序結束。
最大堆你可以把它看成二叉樹,根元素,大於左右節點。

在這裏插入圖片描述


6-2:堆排序代碼

//當列表第一個是以下標0開始,結點下標爲i,左孩子則爲2*i+1,右孩子下標則爲2*i+2,若下標以1開始,左孩子則爲2*i,右孩子則爲2*i+1
public static void heapAdjust(int a[], int s, int m){
    int key = a[s];
    for(int j = 2*s + 1; j <= m; j = 2*j + 1 ){
        // 找到左節點和右節點中小的節點
        if(j < m && a[j] <= a[j+1] ) {
            ++j;
        }
        // 如果當前值比左右節點最小值還小就不用管了
        if( a[j] <= key ){
            break;
        }
        a[s] = a[j];
        s = j;
    }
    a[s] = key;
}
public static void heap_sort(int a[], int size){
    //初始建堆,從最後一個非葉子節點開始
    for(int i = size/2-1; i >= 0; --i){
        heapAdjust(a, i, size-1);
    }
    //取堆頂,並且調整
    int tmp;
    for(int i = size-1; i > 0 ; --i){
        tmp = a[0];
        a[0] = a[i];
        a[i] = tmp;
        heapAdjust(a, 0, i-1);
    }
}



七、歸併排序

7-1:歸併排序的解釋

平均時間複雜度 最好時間複雜度 最壞時間複雜度 空間複雜度 穩定性
O(nlog2n) O(nlog2n) O(nlog2n) O(n) 穩定

把序列儘可能的等分,然後兩半分別排序。排序好後再合併排序

在這裏插入圖片描述


7-2:歸併排序代碼

/**
 * 每個部分進行排序
 */
public void sort(int[] arr,int i,int mid,int j){
    int[] brr = new int[j-i+1];
    int start_one = i;
    int start_two = mid+1;
    int cur = 0;
    while (start_one <= mid && start_two <= j){
        brr[cur ++] = arr[start_one] < arr[start_two] ? arr[start_one ++] : arr[start_two ++];
    }
    while (start_one <= mid){
        brr[cur ++] = arr[start_one++];
    }
    while (start_two <= j){
        brr[cur ++] = arr[start_two++];
    }
    for (int k = 0;k < cur; k++){
        arr[i ++] = brr[k];
    }
}

/**
 * 拆分成一個個部分
 */
public void mergeSort(int[] arr,int i,int j){
    if (i >= j) return;
    int mid = (i + j) / 2;
    mergeSort(arr, i, mid);
    mergeSort(arr,mid + 1, j);
    sort(arr, i,mid,j);
}



八、基數排序

8-1:基數排序的解釋

平均時間複雜度 最好時間複雜度 最壞時間複雜度 空間複雜度 穩定性
O(d(r+n)) O(d(n+rd)) O(nlog2n) O(n) 穩定

基數排序要根據具體的序列規則來做,這裏以最簡單的十位數爲例,作圖。
先以個位數進行排序,然後再以10位數進行排序。回收的結果就是排序好的結果。

在這裏插入圖片描述

8-2:基數排序代碼

因爲基數排序和具體的序列有關,這裏以最簡單的個位數爲例,方便你閱讀代碼。

public static void radixSort(int[] arr){
    // 創建0-9的桶
    List<List<Integer>> lists = new ArrayList<>();
    for (int i = 0;i < 10; i++){
        lists.add(new ArrayList<>());
    }
    // 按照順序把數字裝入桶中
    for (int j = 0;j < arr.length; j++){
        lists.get(arr[j]).add(arr[j]);
    }
    // 按照順序回收桶中的數據
    int cur = 0;
    for (List<Integer> item : lists){
        for (Integer i : item){
            arr[cur ++] = i;
        }
    }
}



測試代碼

你可以使用下面的工具類,對你寫的排序方法進行測試。

/**
 * 測試排序規則
 *  1、測試100次,每次隨機出現0-100的數字
 *  2、arr 使用系統的排序, brr我們自己的排序, crr 不排序
 *  3、對比arr 和 brr看是否相同,如不相同就打印 arr、brr 、crr
 *  4、你每次只需要替換下面註釋地
 *
 * @author 小道仙
 * @date 2020年5月23日
 */
public static boolean testSort(){
    int arrayLenght = 100;
    int[] arr = new int[arrayLenght];
    int[] brr = new int[arrayLenght];
    int[] crr = new int[arrayLenght];
    int tmp,cur;
    for (int i = 0;i < 10000; i++){
        cur = 0;
        for (int j = 0; j < arrayLenght; j++){
            tmp = (int)(Math.random()*100);
            arr[cur] = tmp;
            crr[cur] = tmp;
            brr[cur ++] = tmp;
        }
        Arrays.sort(arr);
       
        try {
            // 每次測試替換掉下面的這個排序方法
            // heap_sort(brr, brr.length);
        }catch (Exception e){
            e.printStackTrace();
        }
        for (int k = 0;k < cur; k++){
            if (arr[k] != brr[k]){
                print(arr,brr,crr);
                return false;
            }
        }
    }
    return true;
}


private static void print(int[] arr,int[] brr,int[] crr){
    System.out.print("arr : ");
    for (int i = 0;i < arr.length; i++){
        System.out.print(arr[i] + " ");
    }

    System.out.println();
    System.out.print("brr : ");
    for (int i = 0;i < brr.length; i++){
        System.out.print(brr[i] + " ");
    }

    System.out.println();
    System.out.print("crr : ");
    for (int i = 0;i < crr.length; i++){
        System.out.print(crr[i] + " ");
    }
    System.out.println();
    System.out.println();
}



關注我吧,一個喜歡胡思亂想的程序員。

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章