1. 程式人生 > >常見的五類排序演算法圖解和實現(歸併類:二路歸併排序)

常見的五類排序演算法圖解和實現(歸併類:二路歸併排序)

歸併類的排序演算法

歸併:將兩個或兩個以上的有序表組合成一個新的有序表。

內部排序中,通常採用的是 2-路歸併排序。即:將兩個位置相鄰的記錄有序子序列歸併為一個記錄有序的序列。歸併排序是建立在歸併操作上的一種有效的排序演算法。該演算法是採用分治法(Divide and Conquer)的一個非常典型的應用。

圖解如下

看成是 n 個有序的子序列(長度為 1),然後兩兩歸併。

得到 n/2 個長度為2 或 1 的有序子序列。繼續亮亮歸併

最後一趟

程式碼如下:

複製程式碼
 1 //二路一次歸併過程的演算法
 2 //low為本次二路歸併排序的第1有序區的第1個元素,i指向第1個元素, mid為第1有序區的最後1個元素
3 void merge(int List[], int low, int mid, int high) 4 { 5 //mid+1為第2有序區第1個元素,mid為第1有序區的最後1個元素 6 //i 指向第 1 有序區的第 1 個元素 7 int i = low; 8 //j 指向第 2 有序區的第 1 個元素,high 為第 2 有序區的最後一個元素 9 int j = mid + 1; 10 //temp陣列暫存合併的有序序列 11 int *temp = new int[high - low + 1]; 12 //設定臨時陣列的指示標誌 k
13 int k = 0; 14 //記憶體分配失敗 15 if(!temp){ 16 cout<<"陣列分配失敗!"; 17 exit(0); 18 } 19 //順序選取兩個有序區的較小元素,儲存到t陣列中,因為是遞增排序 20 while(i <= mid && j <= high){ 21 //較小的元素,存入temp臨時陣列中 22 if(List[i] <= List[j]){ 23 temp[k++] = List[i++];
24 }else{ 25 temp[k++] = List[j++]; 26 } 27 }// end of while 28 //比完之後,假如第1個有序區仍有剩餘,則直接全部複製到 temp 陣列 29 while(i <= mid){ 30 temp[k++] = List[i++]; 31 } 32 //比完之後,假如第2個有序區還有剩餘,則直接全部複製到 temp 陣列 33 while(j <= high){ 34 temp[k++] = List[j++]; 35 } 36 //將排好序的序列,重存回到 list 中 low 到 high 區間 37 for(i = low, k = 0; i <= high; i++, k++){ 38 List[i] = temp[k]; 39 } 40 //delete [] 刪除動態陣列的記憶體 41 delete []temp; 42 } 43 44 //遞迴實現二路歸併排序(也就是分治法的思想) 45 void mergeSort(int List[], int low, int high) 46 { 47 //二路歸併排序,分為二路 48 int mid = (low + high) / 2; 49 //終止條件,low >= high, 不是while,且不含等號,否則死迴圈 50 if(low < high) 51 { 52 //遞迴過程,二路歸併排序遞迴過程 53 mergeSort(List, low, mid); 54 mergeSort(List, mid + 1, high); 55 //歸併 56 merge(List, low, mid, high); 57 } 58 } 59 60 int main(void) 61 { 62 int source[7] = {49, 38, 65, 97, 76, 13, 27}; 63 64 mergeSort(source, 0, 6); 65 66 for (int i = 0; i < 7; i++) { 67 printf(" %d ", source[i]); 68 } 69 70 return 0; 71 }
複製程式碼

上述程式碼使用的是遞迴的方式,遞迴函式裡,遞迴語句之前的語句和各級被調的遞迴函式執行順序一致,而遞迴語句之後的語句和被調的遞迴函式執行順序相反。這就是為什麼 merge 函式要放在遞迴語句(兩條遞迴語句)之後,因為是逆向執行的。聯絡二路歸併排序的過程!!

還可以使用非遞迴的方式,程式碼如下:

複製程式碼
 1 //非遞迴演算法實現二路歸併排序,length代表陣列長度,即陣列最大下標是 legth - 1
 2 void mergeSort(int List[],int length)
 3 {
 4     //回憶圖解的過程,二路歸併演算法的流程,不同於遞迴,遞迴是先遞迴語句,然後歸併函式,這樣歸併函式是倒序執行(和遞迴函式執行順序相反)
 5     int size = 1;
 6     int low;
 7     int mid;
 8     int high;
 9     //size 是標記當前各個歸併序列的high-low,從1,2,4,8,……,2*size
10     while(size <= length - 1)
11     {
12         //從第一個元素開始掃描,low代表第一個分割的序列的第一個元素
13         low = 0;
14         //當前的歸併演算法結束的條件
15         while(low + size <= length - 1)
16         {
17             //mid代表第一個分割的序列的最後一個元素
18             mid = low + size - 1;
19             //high 代表第二個分割的序列的最後一個元素
20             high = mid + size;
21             //判斷一下:如果第二個序列個數不足size個
22             if(high > length - 1){
23                 //調整 high 為最後一個元素的下標即可
24                 high = length - 1;
25             }
26             //呼叫歸併函式,進行分割的序列的分段排序
27             merge(List, low, mid, high);
28             //打印出每次歸併的區間
29             cout << "low:" << low << " mid:" << mid << " high:" << high << endl;
30             //下一次歸併時第一序列的第一個元素位置
31             low = high + 1;
32         }// end of while
33         //範圍擴大一倍,二路歸併的過程
34         size *= 2;
35     }// end of while
36 }
複製程式碼

二路歸併排序演算法分析

每趟歸併的時間複雜度為O(n),共需進行 log2 n 趟。

二路歸併排序的時間複雜度:等於歸併趟數與每一趟時間複雜度的乘積。時間複雜度為O(nlog2n)。

利用二路歸併排序時,需要利用與待排序陣列相同的輔助陣列作臨時單元,故該排序方法的空間複雜度為O(n)比前面介紹的其它排序方法佔用的空間大。

由於二路歸併排序中,每兩個有序表合併成一個有序表時,若分別在兩個有序表中出現有相同排序碼,則會使前一個有序表中相同排序碼先複製,後一有序表中相同排序碼後複製,從而保持它們的相對次序不會改變。所以,二路歸併排序是一種穩定的排序方法。

歸併的思想主要用於外部排序:

外部排序可分兩步 ①待排序記錄分批讀入記憶體,用某種方法在記憶體排序,組成有序的子檔案,再按某種策略存入外存。 ②子檔案多路歸併,成為較長有序子檔案,再記入外存,如此反覆,直到整個待排序檔案有序。 外部排序可使用外存、磁帶、磁碟,最初形成有序子檔案長取決於記憶體所能提供排序區大小和最初排序策略,歸併路數取決於所能提供排序的外部裝置數。