歸並類的排序算法
歸並:將兩個或兩個以上的有序表組合成一個新的有序表。
內部排序中,通常采用的是 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),比前面介紹的其它排序方法占用的空間大。
由於二路歸並排序中,每兩個有序表合並成一個有序表時,若分別在兩個有序表中出現有相同排序碼,則會使前一個有序表中相同排序碼先復制,后一有序表中相同排序碼后復制,從而保持它們的相對次序不會改變。所以,二路歸並排序是一種穩定的排序方法。
歸並的思想主要用於外部排序:
歡迎關注
dashuai的博客是終身學習踐行者,大廠程序員,且專注於工作經驗、學習筆記的分享和日常吐槽,包括但不限於互聯網行業,附帶分享一些PDF電子書,資料,幫忙內推,歡迎拍磚!