這節我們就用的最多的算法——排序發起重點的討論。
常見的排序分為冒泡排序,快速排序,直接插入排序 ,希爾排序,基數排序 ,簡單選擇排序 ,堆排序 等等。
一、冒泡排序
冒泡排序(Bubble Sort)的基本思想是:將相鄰的記錄的關鍵碼進行比較,若前面記錄的關鍵碼大於后面記錄的關鍵碼,則將它們交換,否則不交換。
設待排序的順序表 sqList 中有 n 個記錄,冒泡排序要進行 n-1 趟,每趟循環均是從最后兩個記錄開始。 第 1 趟循環到第 2 個記錄的關鍵碼與第 1 個記錄的關鍵碼比較后終止, 第 2 趟循環到第 3 個記錄的關鍵碼與第 2 個記錄的關鍵碼比較結束后終止。一般地,第 i 趟循環到第 i+1 個記錄的關鍵碼與第 i 個記錄的關鍵碼比較后終止,所以,第 n-1 趟循環到第 n 個記錄的關鍵碼與第 n-1 個記錄的關鍵碼比較后終止。
冒泡排序算法的實現如下所示,算法中記錄的比較表示記錄關鍵碼的比較,順序表中只存放了記錄的關鍵碼: 實現的源代碼如下:
//進行冒泡排序的方法 public void BubbleSort(SeqList<int> sqList) { ///進行交換的變量 int tmp; for (int i = 0; i < sqList.Last; ++i) { for (int j = sqList.Last - 1; j >= i; --j) { //如果后面比前面大, 就交換 if (sqList[j + 1] < sqList[j]) { tmp = sqList[j + 1]; sqList[j + 1] = sqList[j]; sqList[j] = tmp; } } } } //由於是雙層循環 算法的時間的復雜度是O(n^2)
冒泡排序算法的最好情況是記錄已全部排好序,這時,循環 n-1 次,每次循環都因沒有數據交換而退出。因此,冒泡排序算法在最好情況下的時間復雜度為O(n)。
總的移動次數為比較次數的 3 倍, 因為被進行一次比較, 需要進行 3 次移動。因此,冒泡排序算法在最壞情況下的時間復雜度為O(n2)。
冒泡排序算法只需要一個輔助空間用於交換記錄,所以,冒泡排序算法是一種穩定的排序方法。什么是穩定的排序算法,就是前面與后面的數字相等的話,不用交換。
二、快速排序
快速排序(Quick Sort)的基本思想是:通過不斷比較關鍵碼,以某個記錄為界(該記錄稱為支點) ,將待排序列分成兩部分。其中,一部分滿足所有記錄的關鍵碼都大於或等於支點記錄的關鍵碼, 另一部分記錄的關鍵碼都小於支點記錄的關鍵碼。把以支點記錄為界將待排序列按關鍵碼分成兩部分的過程,稱為一次划分。對各部分不斷划分,直到整個序列按關鍵碼有序為止。
設待排序的順序表 sqList 中有 n 個記錄,一般地,第一次划分把第一個記錄作為支點。首先,把支點復制到一個臨時存儲空間中,並設兩個指示器,一個指示器 low,指向順序表的低端(第一個記錄所在位置) ,一個指示器 high,指向順序表的高端(最后一個記錄所在位置) 。然后,從 high 所指向的記錄開始,將記錄的關鍵碼與支點(在臨時存儲空間中)的關鍵碼進行比較,如果 high 所指向的記錄的關鍵碼大於支點的關鍵碼,high 指示器向順序表的低端方向移動一個記錄的位置,否則,將 high 所指的記錄復制到 low 所指的存儲空間中。接着,又將 low 移到下一個記錄,從 low 所指向的記錄開始,將記錄的關鍵碼與臨時存儲空間的記錄的關鍵碼進行比較, 如果 low 所指向的記錄的關鍵碼小於臨時存儲空間的記錄的關鍵碼,low 指示器向順序表的高端方法移動一個記錄的位置,否則,將 low 所指的記錄復制到 high 所指的存儲空間中,high 指示器向順序表的低端移動一個記錄的位置。如此重復,直到 low 和 high 指示器指向同一個記錄,
將臨時空間的記錄賦給 low 所指向的存儲空間,第一次划分結束。如圖所示:
快速排序的算法實現如下所示,算法中記錄的比較表示記錄關鍵碼的比較順序表中只存放了記錄的關鍵碼。相應源代碼如下:
1 public void QuickSort(SeqList<int> sqList, int low, int high) 2 { 3 //頭指針 4 int i = low; 5 //尾指針 6 int j = high; 7 //關鍵碼 8 int tmp = sqList[low]; 9 //頭指針尾指針位置的變換 10 while (low < high) 11 { 12 while ((low < high) && (sqList[high] >= tmp)) 13 { 14 --high; 15 } 16 sqList[low] = sqList[high]; 17 ++low; 18 while ((low < high) && (sqList[low] <= tmp)) 19 { 20 ++low; 21 } 22 sqList[high] = sqList[low]; 23 --high; 24 } 25 sqList[low] = tmp; 26 //新一輪的變換 27 if (i < low-1) 28 { 29 QuickSort(sqList, i, low-1); 30 } 31 //新一輪的遞歸 32 if (low+1 < j) 33 { 34 QuickSort(sqList, low+1, j); 35 } 36 } 37 } 38 //由於用到遞歸 循環 時間復雜度是O(nlog2n)
快速排序算法的時間復雜度和每次划分的記錄系很大。 如果每次選取的記錄都能均分成兩個相等的子序列,這樣的快速排序過程是一棵完全二叉樹結構(即每個結點都把當前待排序列分成兩個大小相當的子序列結點,n個記錄待排序列的根結點的分解次數就構成了一棵完全二叉樹) ,這時分解次數等於完全二叉樹的深度log2n。每次快速排序過程無論把待排序列這樣划分,全部的比較次數都接近於n-1 次,所以,最好情況下快速排序的時間復雜度為O(nlog2n) 。快速排序算法的最壞情況是記錄已全部有序,此時n個記錄待排序列的根結點的分解次數就構成了一棵單右支二叉樹。 所以在最壞情況下快速排序算法的時間復雜度為O(n2)。一般情況下,記錄的分布是隨機的,序列的分解次數構成一棵二叉樹,這樣二叉樹的深度接近於log2n,所以快速排序算法在一般情況下的時間復雜度為O(nlog2n) 。 另外,快速排序算法是一種不穩定的排序的方法。不穩定排序的算法 ,前后數字相等要交換。
這節,我們介紹了快速排序和冒泡排序。下節,我們介紹直接插入排序和希爾排序。嘎嘎。