一、歸並(Merge)
1. 概念
將兩個有序數列合並成一個有序數列,我們稱之為“歸並”。
2. 算法思路及實現
設兩個有序的子序列(相當於輸入序列)放在同一序列中相鄰的位置上:array[low..m],array[m + 1..high],先將它們合並到一個局部的暫存序列 temp (相當於輸出序列)中,待合並完成后將 temp 復制回 array[low..high]中,從而完成排序。
在具體的合並過程中,設置 i,j 和 p 三個指針,其初值分別指向這三個記錄區的起始位置。合並時依次比較 array[i] 和 array[j] 的關鍵字,取關鍵字較小(或較大)的記錄復制到 temp[p] 中,然后將被復制記錄的指針 i 或 j 加 1,以及指向復制位置的指針 p 加 1。重復這一過程直至兩個輸入的子序列有一個已全部復制完畢(不妨稱其為空),此時將另一非空的子序列中剩余記錄依次復制到 array 中即可。
c語言實現如下:
void merge(int* array, int low, int mid, int high) { assert(array && low >= 0 && low <= mid && mid <= high); int* temp = (int*)malloc((high - low + 1) * sizeof(int)); if (!temp) { printf("Error:out of memory!"); return; } int i = low; int j = mid + 1; int index = 0; while (i <= mid && j <= high) { if (array[i] <= array[j]) { temp[index++] = array[i++]; } else { temp[index++] = array[j++]; } } while (i <= mid) { temp[index++] = array[i++]; } while (j <= high) { temp[index++] = array[j++]; } memcpy((void*)(array + low), (void*)temp, (high - low + 1) * sizeof(int)) ; free(temp); }
二、 歸並排序(Merge Sort)概念
建立在歸並操作上的一種排序算法,該算法是采用分治法(Divide and Conquer)的一個非常典型的應用。
歸並排序有多路歸並排序、兩路歸並排序,可用與內排序,也可用於外排序,本文僅對內排序的兩路歸並方法進行討論。
三、歸並排序思路及實現
1. 從下往上的歸並排序
- 把n個記錄看成n個長度為1的數列(因為長度為1,所以自然有序)。
- 進行兩兩歸並,得到n/2個長度為2的有序數列,再將這些數列兩兩合並,得到n/4個長度為4的有序數列,重復這個過程,直到合並成一個數列為止。
過程如下圖所示:
C語言實現:
// 對 [0, length - 1] 做一趟歸並長度為 n 的歸並排序 void merge_pass(int* array, int length, int n) { assert(array && length >= 1 && n >= 1); int i; int sortLength = 2 * n; // 歸並長度為 n 的兩個相鄰子序列 for(i = 0; i + sortLength - 1 < length; i = i + sortLength) { merge(array, i, i + n - 1, i + sortLength - 1); } // 若 i + n - 1 < length - 1,則剩余一個子文件輪空,無須歸並。 // 尚有兩個子序列,其中后一個長度小於 n, 歸並最后兩個子序列。 if (length - 1 > i + n - 1) { merge(array, i, i + n - 1, length - 1); } } // 用分治法自下向上進行二路歸並排序 // void merge_sort(int* array, int length) { assert(array && length >= 0); int n; for(n = 1; n < length; n = (n << 1)) { merge_pass(array, length, n); } }
優點是效率高,缺點是代碼可讀性差。
2. 從上往下的歸並排序
具體的實現有三個步驟:
- 分解 -- 將當前區間一分為二,即求分裂點 mid = (low + high)/2;
- 求解 -- 遞歸地對兩個子區間a[low...mid] 和 a[mid+1...high]進行歸並排序。遞歸的終結條件是子區間長度為1。
- 合並 -- 將已排序的兩個子區間a[low...mid]和 a[mid+1...high]歸並為一個有序的區間a[low...high]。
如下圖所示:
C語言實現:
void merge_sort_dc_impl(int* array, int low, int high) { assert(array && low >= 0); int mid; if (low < high) { mid = (low + high) >> 1; merge_sort_dc_impl(array, low, mid); merge_sort_dc_impl(array, mid + 1, high); merge(array, low, mid, high); } } // 用分治法自上向下進行排序 void merge_sort_dc(int* array, int length) { assert(array && length >= 0); merge_sort_dc_impl(array, 0, length - 1); }
四、時間復雜度及穩定性
1. 時間復雜度
長度為n的序列需要進行logn次二路歸並才能完成排序(歸並排序的形式其實就是一棵二叉樹,需要遍歷的次數就是二叉樹的深度),而每趟歸並的時間復雜度為O(n),因此歸並排序的時間復雜度為O(nlogn)。
2. 排序穩定性
所謂排序穩定性,是指如果在排序的序列中,存在兩個相等的兩個元素,排序前和排序后他們的相對位置不發生變化的話,我們就說這個排序算法是穩定的。
排序算法是穩定的算法。
五、參考
1. 排序算法之歸並排序
2. 歸並排序
(完)