資料結構(歸併排序)
1,二路歸併排序設計思路
與快速排序一樣,歸併排序也是基於分治策略的排序,(分治法將問題分(divide)成一些小的問題然後遞迴求解,而治(conquer)的階段則將分的階段得到的各答案"修補"在一起,即分而治之)。歸併排序將待排序的元素分成兩個長度相等的子序列,分別為每一個子序列排序,然後把子序列合併成一個總的序列。合併兩個子序列的過程稱為二路歸併。(注:二路歸併演算法是一種穩定排序演算法,他的執行時間不依賴與待排元素的初始序列,因此也就避免了快速排序最差的情況),演算法時間複雜度,空間複雜度
1.1,歸併排序的遞迴演算法:
void MergeSort(int *arr, int left, int right) { //left和right是當前參加歸併的元素下標 if (left < right) { int mid = (right + left); MergeSort(arr, left, mid); MergeSort(arr, mid + 1, right); Merge(arr, left, right, mid); } } void Merge(int *arr, int left, int right, int mid) { //arr[left......mid]和arr[mid+1.........right]是兩個有序表將這兩個表合併成一個有序表,先暫時放在array陣列中,再重新放回原來的位置 int i = left, j = mid + 1, k = 0, s = right - left + 1;//i和j是檢測指標,k是存放指標 int *array = new int[s]; while (i <= mid && j <= right)//如果兩個表都沒檢測完,則兩兩比較 { if (arr[i] <= arr[j]) array[k++] = arr[i++]; else array[k++] = arr[j++]; } while (i <= mid)//第一個表沒有檢測完 { array[k++] = arr[i++]; } while (j <= right)//第二個表沒有檢測完 { array[k++] = arr[j++]; } for (int i = 0;i < s;i++) arr[left + i] = array[i]; free(array); }
我們可以把分解的過程理解為建立完全二叉樹的過程(遞迴深度是)
在這裡給大家列出(2,4,7,5,8,1,3,6)這個序列歸併排序的過程,從圖中我們可以看到,每次都是在原有元素的基礎上面對半分,即取一半,直到分解為每一組元素剩下一個為止,我們預設一個元素已經有序,然後在依次把每兩個元素合併成一個有序序列,即上圖中的第一步合併操作,從那裡我們可以看到現在已經合併為四個分組,每個分組裡面兩個元素有序排列,接著在進行一次合併,合併為兩個分組,每組四個元素有序排列,接著進行最後一次合併操作,整個序列變為有序,排序結束。
1.2,二路歸併排序迭代演算法
但是在實際上遞迴的歸併排序時間代價和空間代價都很高,應為要做多次遞迴呼叫,最多需要
//歸併排序的迭代演算法實現 void Merge(int *arr1, int *arr2,int left, int right, int mid) { //arr[left......mid]和arr[mid+1.........right]是兩個有序表將這兩個表合併成一個有序 表,先暫時放在array陣列中,再重新放回原來的位置 int i = left, j = mid + 1, k = 0, s = right - left + 1;//i和j是檢測指標,k是存放指標 while (i <= mid && j <= right)//如果兩個表都沒檢測完,則兩兩比較 { if (arr1[i] <= arr1[j]) arr2[k++] = arr1[i++]; else arr2[k++] = arr1[j++]; } while (i <= mid)//第一個表沒有檢測完 { arr2[k++] = arr1[i++]; } while (j <= right)//第二個表沒有檢測完 { arr2[k++] = arr1[j++]; } } void MergePass(int *arr1, int *arr2, int len,int n) { //對arr1中的長度為len的歸併項進行一趟二路歸併,結果存放於arr2的相同位置 int i = 0; while (i + 2 * len <= n - 1)//兩兩歸併長度為len的歸併項,批處理歸併長度為len的歸併項 { Merge(arr1, arr2, i, i + 2 * len - 1, i + len - 1);//arr1[i......i+len-1]與ar1[i+len.....i+2*len-1] i = i + 2 * len;//i進到下一次兩兩歸併的第一個歸併項 } if (i + len <= n)//特殊情況第二個歸併項不足len { Merge(arr1, arr2, i, n - 1, i + len - 1); } else//特殊情況,只剩下一個歸併項 for (int j = i;j <= n - 1;j++) { arr2[j] = arr1[j]; } } void MergeSort(int *arr, int n) { int i, len = 1; int *arr2 = new int[n]; while (len < n)//第一趟歸併令長度len為1,以後每次歸併都讓len變為原來2倍 { MergePass(arr, arr2, len,n); len *= 2; MergePass(arr2, arr, len, n); len *= 2; } }
下面我們以一個例項來演示迭代的歸併排序,初始序列是(21,25,49,25*,93,62,72,08,37,16,54)在這裡為了說明排序是穩定的,用了25*做對照,25和25*相對位置沒有變,所以歸併排序是穩定的。
len=1:21,25,49,25*,93,62,72,08,37,16,54
len=2:21 25,25* 49,62 93,08 72,16 37,54
len=4,:21 25 25* 49,08 62 72 93,16 37 54
len=8:08 21 25 25* 49 62 72 93,16 37 54
len=16:08 16 21 25 25* 37 49 54 62 72 93
當len=1時,我們把每個元素都看作歸併項,歸併為len=2的歸併項,每一次len都增大原來的二倍。假設陣列元素arr[0]到arr[n-1]已經歸併為長度為len的歸併項,要求再將這些歸併項兩兩歸併,歸併成長度為2len的歸併項,把這些歸併項放在arr2的輔助陣列中,如果n不是2len的整數倍,則歸併完一趟,可能遇到兩種情況:
(1)剩下一個長度為len的歸併項和一個長度小於len的歸併項,這時可以再次呼叫Merge函式將他們歸併稱為一個長度小於2len的歸併項。
(2)只剩下一個歸併項,長度小於或者等於len,由於沒有另一個歸併項與其歸併,可以將直接放到arr2的輔助陣列中,準備參加下一趟歸併操作。
總之,迭代演算法分為兩部分,第一部分先對長度為len的歸併項進行兩兩歸併,直到出現上述情況之一,第二部分處理上述特殊情況。