[-演算法篇-] 排序

張風捷特烈發表於2019-04-19
本文測試資料:
private static int[] arr =  {8, 3, 5, 55, 7, 22, 32, 99};
複製程式碼

一、氣泡排序

1.第一版:

迴圈28次----移動6次

private static void bubbleSort(int arr[]) {
    int n = arr.length - 1;
    int i, j, t;
    for (i = 0; i < n; i++) {//遍歷陣列
        for (j = i + 1; j <= n; j++) {//內層遍歷,從i的下一個
            if (arr[i] > arr[j]) {// 陣列i元素大,則交換
                t = arr[j];
                arr[j] = arr[i];
                arr[i] = t;
            }
        }
    }
}
複製程式碼
  • i=0,第一趟
j 從 1 開始,arr[0]>arr[1],交換位置,保證大的數在後面,a[0]=3
j +1 = 2 , 比較a[2]和a[0],arr[0]<arr[2],繼續
然後j +1 =3 ,就這樣依次將j右移和a[0]比較,這樣到最後一個可保證a[0]是最小的
複製程式碼

[-演算法篇-] 排序

  • i=1,第二趟
j 從 2 開始,arr[1]>arr[2],交換位置,保證大的數在後面,a[1]=5
j +1 = 2 , 比較a[3]和a[1],arr[1]<arr[3],繼續
然後j +1 =3 ,就這樣依次將j右移和a[0]比較,這樣到最後,可保證a[0],a[1]正確排序
複製程式碼

2019-04-19 08 39 39.png

然後同理,經過n趟,小的數移到了前面


2.相鄰元素冒泡

迴圈28次----移動6次

private static void bubbleSort1(int[] arr) {
    int n = arr.length - 1;
    int i, j, t;
    for (i = 0; i < n; i++) {
        for (j = n; j > i; j--) {
            if (arr[j - 1] > arr[j]) {// 相鄰元素兩兩對比,如果前者大,交換位置
                t = arr[j - 1];
                arr[j - 1] = arr[j];
                arr[j] = t;
            }
        }
    }
}
複製程式碼
  • i=0,第一趟

相鄰兩個元素比較,小的往左走

[-演算法篇-] 排序

  • i=1,第二趟

相鄰兩個元素比較,小的往左走

[-演算法篇-] 排序


3.優化版冒泡

迴圈22次----移動6次

//		  平均T複雜度	穩定	  最好	最壞	 輔助空間
//氣泡排序: O(n^2)	    YES      O(n)   O(n^2)  O(1)
private static void bubbleSort2(int[] arr) {
    int n = arr.length - 1;
    int i, j, t;
    boolean flag = true;
    for (i = 0; i < n && flag; i++) {
    //如果退出j迴圈時flag=false,說明本迴圈沒有元素交換,說明已排序完成
        for (j = n; j > i; j--) {
            flag = false;
            if (arr[j - 1] > arr[j]) {// 相鄰元素兩兩對比,如果前者大,交換位置
                t = arr[j - 1];
                arr[j - 1] = arr[j];
                arr[j] = t;
                flag = true;
            }
        }
    }
}
複製程式碼

二、選擇排序

迴圈28次----移動6次

//		  平均T複雜度	穩定	  最好	 最壞	 輔助空間
//選擇排序: O(n^2)	    YES     O(n^2)   O(n^2)  O(1)
public static void selectSort(int[] arr) {
    int n = arr.length - 1;
    int i, j, t, min;
    for (i = 0; i < n; i++) {
        min = i;
        for (j = i + 1; j <= n; j++) {
            if (arr[min] > arr[j]) {
                min = j;
            }
        }
        if (min != i) {//如果min和i相同,沒必要交換
            t = arr[i];//交換
            arr[i] = arr[min];
            arr[min] = t;
        }
    }
}
複製程式碼

在j迴圈中記錄發現的最小值所在索引,如果min不是i,交換元素

[-演算法篇-] 排序


三、插入排序

1.具體演算法
//		  平均T複雜度	穩定	  最好	最壞	 輔助空間
//插入排序: O(n^2)	    YES      O(n)   O(n^2)  O(1)
public static int[] insertSort(int[] arr) {
    int i, j, t;
    int n = arr.length;
    for (i = 1; i < n; i++) {//從i點開始遍歷陣列
        t = arr[i];//使用temp變數記錄i索引所在值
        for (j = i; j > 0 && t < arr[j - 1]; j--) {//從i點向左遍歷
            arr[j] = arr[j - 1];//當遇到比temp大的數,就將前一個元素後推
        }
        arr[j] = t;//否則j位變成t
    }
    return arr;
}
複製程式碼

2.分析
  • 第一輪排序

第一輪排序.png

i,j從索引1開始, t=3 , 
t與j前一個元素比較, 3<8 , j處值變成較大值 8 , j 向左指一位
跳出j的for迴圈後,將j索引置為剛才儲存的臨時變數t=3,此時前兩個元素排序完成。
複製程式碼
  • 第二輪排序

第二輪排序.png

i索引右移一格到2,t= 5 ,j 從索引2開始
t與j前一個元素比較, 5 < 8 , j處值變成較大值 8 , j 向左指一位
t與j前一個元素比較, 5 > 3 , 跳出j迴圈
將j索引置為剛才儲存的臨時變數t=3,此時前3個元素排序完成。
複製程式碼
  • 第三輪排序

第三輪排序.png

i索引右移一格到3,t= 55 ,j 從索引3開始
t與j前一個元素比較, 55 > 8 , 跳出j迴圈
將j索引置為剛才儲存的臨時變數t=55,此時前4個元素排序完成。
複製程式碼
  • 第四輪排序

第四輪排序.png

i索引右移一格到4,t= 7 ,j 從索引4開始
t與j前一個元素比較, 7 <55 , j處值變成較大值 55 , j 向左指一位 為3 
t與j前一個元素比較, 7 < 8 , j處值變成較大值 8 ,  j 向左指一位 為2 
t與j前一個元素比較, 7 > 5 , 跳出j迴圈 
將j索引置為剛才儲存的臨時變數t=7,此時前5個元素排序完成。
複製程式碼

插入排序第n輪排序後將前n+1個元素是順序的


四、希爾排序

試了一下開篇中100W的隨機整數,果然不愧為N*logN的演算法, 0.365秒

//		  平均T複雜度	穩定	  最好		最壞	  輔助空間
//希爾排序:O(nlogn)	  NO     O(n^1.3)	 O(n^2)   O(1)
public static void shellSort(int[] arr) {
    int i, j, t , gap;
    int n = arr.length;
    for (gap = n / 2; gap > 0; gap /= 2) {
        for (i = gap; i < n; i++) {
            t = arr[i];
            for (j = i; j >= gap && t < arr[j - gap]; j -= gap) {
                arr[j] = arr[j - gap];
            }
            arr[j] = t;
        }
    }
}
複製程式碼

希爾排序.png

希爾排序先將資料歸整,最後gap=1時,相當於歸整後的插入排序

開始gap取4,圖中相同顏色進行比較,用的是插入排序交換的套路
然後gap減半=2,圖中相同顏色進行比較,用的是插入排序交換的套路
然後gap減半=1,圖中相同顏色進行比較,用的是插入排序交換的套路
複製程式碼

五、堆排序

1.具體演算法

試了一下開篇中100W的隨機整數,果然不愧為N*logN的演算法, 0.286秒

//		  平均T複雜度	穩定	  最好		最壞	  輔助空間
//堆排序:O(nlogn)		NO	 O(nlogn)	O(nlogn)	O(1)
private static void heapSort(int[] arr) {//左半的元素
    for (int i = arr.length / 2 - 1; i >= 0; i--) {
        perDownHeap(arr, i, arr.length);//形成堆
    }
    for (int i = arr.length - 1; i > 0; i--) {
        swapRef(arr, 0, i);//交換首
        perDownHeap(arr, 0, i);//維持堆形狀
    }
}

private static void swapRef(int[] arr, int i, int j) {
    int t = arr[i];
    arr[i] = arr[j];
    arr[j] = t;
}

private static void perDownHeap(int[] arr, int i, int n) {
    int child;
    int t;
    for (t = arr[i]; leftChild(i) < n; i = child) {
        child = leftChild(i);
        if (child != n - 1 && arr[child] < arr[child + 1]) {
            child++;
        }
        if (t < arr[child]) {
            arr[i] = arr[child];
        } else {
            break;
        }
    }
    arr[i] = t;
}
複製程式碼

2.分析

化為無序樹.png

第一次執行的是:perDownHeap(arr, 3, arr.length);
private static void perDownHeap(int[] arr, int i, int n) {
    int child;//0
    int t;//55
    for (t = arr[i]; leftChild(i) < n; i = child) {
        child = leftChild(i);//7  arr[child]=99 即 55的左子是99
        if (child != n - 1 && arr[child] < arr[child + 1]) {//此時條件不滿足
            child++;
        }
        if (t < arr[child]) {//走這裡 55<99 條件滿足
            arr[i] = arr[child];//arr[3]=99
        } else {
            break;
        }
    }
    arr[i] = t;//跳出迴圈, i = child ,arr[7]=t=55
}

第二次執行的是:perDownHeap(arr, 2, arr.length);
private static void perDownHeap(int[] arr, int i, int n) {
    int child;//0
    int t;//5
    for (t = arr[i]; leftChild(i) < n; i = child) {
        child = leftChild(i);//5  arr[child]=22 即 5的左子是22 ,右子是arr[child + 1]=32
        if (child != n - 1 && arr[child] < arr[child + 1]) {//此時條件滿足
            child++;//child = 6 
        }
        if (t < arr[child]) {//走這裡 5<32 條件滿足
            arr[i] = arr[child];//arr[2]=32
        } else {
            break;
        }
    }
    arr[i] = t;//跳出迴圈, i = child ,arr[6]=t=5
}

>其他同上...繪圖如下
複製程式碼

形成堆.png

接下來通過交換根節點和i,再對堆進行沉浮,形成小頂堆

for (int i = arr.length - 1; i > 0; i--) {
    swapRef(arr, 0, i);
    perDownHeap(arr, 0, i);
}
複製程式碼

[-演算法篇-] 排序

[-演算法篇-] 排序

[-演算法篇-] 排序

[-演算法篇-] 排序


六、歸併排序

1.具體演算法
private static void mergeSort(int[] arr) {
    int[] temArr = new int[arr.length];//臨時陣列
    mergeSort(arr, temArr, 0, arr.length - 1);
}

private static void mergeSort(int[] arr, int[] temArr, int left, int right) {
    if (left < right) {
        int center = (left + right) / 2;
        mergeSort(arr, temArr, left, center);//左歸併,使得左子序列有序
        mergeSort(arr, temArr, center + 1, right);//右歸併,使得右子序列有序
        merge(arr, temArr, left, center + 1, right);//兩個子序列合併
    }
}

private static void merge(int[] arr, int[] temArr, int leftPos, int rightPos,
    int leftEnd = rightPos - 1;//左起點比右終點大1
    int tempPos = leftPos;//臨時陣列索引
    int count = rightEnd - leftPos + 1;//此次合併的元素個數
    while (leftPos <= leftEnd && rightPos <= rightEnd) {//說明到頭了
        boolean rightBig = arr[leftPos] <= arr[rightPos];//左邊大?
        //temArr[tempPos]取較小者,tempPos和較小者索引++
        temArr[tempPos++] = rightBig ? arr[leftPos++] : arr[rightPos++];
    }
    while (leftPos <= leftEnd) {
        temArr[tempPos++] = arr[leftPos++];
    }
    while (rightPos <= rightEnd) {
        temArr[tempPos++] = arr[rightPos++];
    }
    for (int i = 0; i < count; i++, rightEnd--) {//將臨時陣列元素放到原陣列
        arr[rightEnd] = temArr[rightEnd];
    }
}
複製程式碼

2.分析

分治.png

|--- 第一次merge: merge(arr, temArr, 0, 1, 1)

leftPos:0
leftEnd:0
arr[leftPos]:8

rightPos:1
rightEnd:1
arr[rightPos]:3

temArr[0] = arr[leftPos]和arr[rightPos]的較小者:arr[rightPos]=3
temArr[1] = arr[leftPos++] = 8 
將臨時陣列元素放到原陣列

|--- 第二次merge: merge(arr, temArr, 2, 3, 3)

leftPos:2
leftEnd: 2
arr[leftPos]:5

rightPos:3
rightEnd:3
arr[rightPos]:55

temArr[0] = arr[leftPos]和arr[rightPos]的較小者 arr[leftPos]=5
temArr[1] = arr[rightPos++] = 55
將臨時陣列元素放到原陣列

|--- 第三次merge: merge(arr, temArr, 0, 2, 3)

leftPos:0
leftEnd: 1
arr[leftPos]:3

rightPos:2
rightEnd:3
arr[rightPos]:5
見下圖...
複製程式碼
  • 第三次 merge

[-演算法篇-] 排序


七、快速排序

1.具體演算法
//		  平均T複雜度	穩定	 最好	最壞	  輔助空間
//快速排序:O(nlogn)    NO     O(nlogn)	O(n^2)	O(n)~O(nlogn)
public static void fastSort(int[] arr) {
    fastSort(arr, 0, arr.length - 1);
}

public static void fastSort(int[] arr, int start, int end) {
    int i, j, key, t;
    if (start >= end) {
        return;
    }
    i = start + 1;
    j = end;
    key = arr[start];//基準位
    while (i < j) {//保證i比j大
        //當j處值<=key,j向←--走 :說明從右找到到第一個大於key的數
        while (key <= arr[j] && i < j) j--;
        //當i處值>=key,i向--→走 :說明從右找到到第一個小於key的數
        while (key >= arr[i] && i < j) i++;
        if (i < j) {//交換
            t = arr[j];
            arr[j] = arr[i];
            arr[i] = t;
        }
    }
    arr[start] = arr[i];//交換基準位
    arr[i] = key;
    fastSort(arr, start, j - 1);//左半
    fastSort(arr, j + 1, end);//右半
}
複製程式碼

分析:
  • Q1:第一趟sort

第一趟.png

此時呼叫了:
 sort(arr, 0, 2);//對左半元素快速排序
複製程式碼
  • Q1.1:Q1左半sort

Q1左半sort

  • Q1.1.1:Q1.1左半sort

Q1.1左半sort

  • Q1.1.2:Q1.1右半sort :一個元素不用排序,直接return

  • Q1.2:Q1右半sort:套路一樣,就不分析了

[-演算法篇-] 排序


小結:沒有最好的排序,只有最適合的排序。
條目 平均T複雜度 穩定 最好 最壞 輔助空間
氣泡排序 O(n^2) YES O(n) O(n^2) O(1)
選擇排序 O(n^2) NO O(n^2) O(n^2) O(1)
插入排序 O(n^2) YES O(n) O(n^2) O(1)
希爾排序 O(nlogn) NO O(n^1.3) O(n^2) O(1)
堆排序 O(nlogn) NO O(nlogn) O(nlogn) O(1)
歸併排序 O(nlogn) YES O(nlogn) O(nlogn) O(n)
快速排序 O(nlogn) NO O(nlogn) O(n^2) O(n)~O(nlogn)
資料量較小時:氣泡排序,選擇排序,插入排序就可以了
資料量非常大時:最好用O(nlogn)複雜度的演算法
如果資料基本是有序的:插入排序
資料隨機性很大:快速排序
需要要求穩定性且O(nlogn):歸併排序是唯一的選擇,代價是需要多消耗一倍空間
如果不想出現最壞情況,也就是想一切在預期之內:堆排序是你不二的選擇
複製程式碼
  • 排序的穩定性:大小相同的元素在排序前後順序有沒有可能改變
設:Ki=Kj (1<=i<=n,1<=j<=n,i!=j)且在排序前ri先於rj(即i<j)
|--- 排序後ri仍先於rj,則排序方法穩定,反之,不穩定
複製程式碼

下面用七張圖記錄一下:

1.氣泡排序.png

2.選擇排序.png

3.插入排序.png

4.希爾排序.png

5.堆排序.png

6.歸併排序.png

7.快速排序.png

ok,就先這樣,以後有想到什麼再補充

相關文章