歸併排序
介紹
歸併排序和快速排序一樣,都是基於分治思想的應用。
透過遞迴,不斷將原數列分為兩個數列,然後再分別使其有序,最後透過歸併將兩個有序子數列合併為新的有序數列。
值得注意的是,與快速排序不同,歸併排序是穩定的。
程式碼實現
void merge_sort(int a[], int l, int r)
{
if (l >= r) return;//判斷區間資料個數,為1則返回
int tmp[100001];//建立臨時陣列
int mid = l + r >> 1;
merge_sort(a, l, mid);
merge_sort(a, mid + 1, r);
int k = 0, i = l, j = mid + 1;//建立雙指標
while (i <= mid && j <= r)//其中一指標遍歷至結尾則終止
if (a[i] <= a[j]) tmp[k++] = a[i++];//對比資料,誰小先加誰
else tmp[k++] = a[j++];
while(i<=mid) tmp[k++] = a[i++];
while(j<=r) tmp[k++] = a[j++];//將剩餘資料依次放入
for (i = l, j = 0; i <= r; i++, j++) a[i] = tmp[j];//將排好的資料放回原陣列
}
思路分析
void merge_sort(int a[], int l, int r)
{
if (l >= r) return;//判斷區間資料個數,為1則返回
int tmp[100001];//建立臨時陣列
int mid = l + r >> 1;
merge_sort(a, l, mid);
merge_sort(a, mid + 1, r);
和快速排序先排序後遞迴不同,歸併排序是先遞迴,無限細分,重點在於回溯時的歸併,當遞迴到陣列區間內只有1個資料時,肯定是有序的,經過歸併後返回的陣列肯定也是有序的,所以我們這裡假設這個函式已經能夠實現目的,將一個陣列分為兩部分然後分別排序,只需要用標記區間的起始位置和結束位置,而兩個區間的分界就是mid
,而歸併的時候我們需要一個臨時存放資料的臨時陣列tmp[]
。
int k = 0, i = l, j = mid + 1;//建立雙指標
while (i <= mid && j <= r)//其中一指標遍歷至結尾則終止
if (a[i] <= a[j]) tmp[k++] = a[i++];//對比資料,誰小先加誰
else tmp[k++] = a[j++];
這裡便是整個演演算法最關鍵的地方:陣列的歸併。
首先,我們知道,兩個區間已經分別有序,而第一個區間起點便是本次函式傳入的起點l
,終點為中值mid
,第二個區間起點為mid+1
,終點為函式傳入的終點引數r
。
我們建立雙指標li
和j
,初始位置分別指向兩個取件的起始位置,不斷互相比較,值更小的放入陣列然後往後遍歷,這樣就能實現整個陣列的歸併,值得注意的是,我們的判斷條件為if(a[i]<=a[j])
,這樣,就能保證同樣大小的資料按照原先的順序依次進入臨時陣列,實現歸併排序的穩定性。
最終,當其中一個指標遍歷到區間結尾時,結束迴圈。
while(i<=mid) tmp[k++] = a[i++];
while(j<=r) tmp[k++] = a[j++];//將剩餘資料依次放入
for (i = l, j = 0; i <= r; i++, j++) a[i] = tmp[j];//將排好的資料放回原陣列
當然,我們還需要將另外一個區間剩下的資料依次擠入臨時陣列,實現最終的排序,那我們就透過比較指標和終點的值來實現。
最後將排好的資料從臨時陣列中依次賦給原陣列。
至此,我們完成了整個歸併排序演演算法的閉環,完成了功能的實現。
結尾
歸併排序和快速排序都是分治思想的體現,他們的思路不僅僅能拿來排序,也能解決一些具體問題,具體選擇哪一種演演算法,就要依靠你細緻入微的觀察,結合題目的特性。
最後,歸併排序和快速排序的時間複雜度一樣,都是O(nlogn)
。
以上便是對歸併排序的介紹,本文由涼茶coltea撰寫,思路來自AcWing,大佬yxc的課程。