2-路歸併排序的核心操作是將一維數組中前後相鄰的兩個有序序列歸併為一個有序序列.其時間複雜度為O(NlgN),空間複雜度為O(N).是一種穩定的排序方法.
1、演算法基本思路
設兩個有序的子檔案(相當於輸入堆)放在同一向量中相鄰的位置上:R[low..m],R[m+1..high],先將它們合并到一個局部的暫存向量R1(相當於輸出堆)中,待合并完成後將R1複製回R[low..high]中。
合并過程
合并過程中,設定i,j和p三個指標,其初值分別指向這三個記錄區的起始位置。合并時依次比較R[i]和R[j]的關鍵字,取關鍵字較小的記錄複製到R1[p]中,然後將被複製記錄的指標i或j加1,以及指向複製位置的指標p加1。
重複這一過程直至兩個輸入的子檔案有一個已全部複製完畢(不妨稱其為空白),此時將另一非空的子檔案中剩餘記錄依次複製到R1中即可。
歸併演算法如下:
void Merge(int * a,int low,int mid,int high){ int i=low,j=mid+1,p=0; int * r=new int[high-low+1]; while(i<=mid && j<=high) { r[p++]=(a[i]<=a[j])?a[i++]:a[j++]; } while(i<=mid) r[p++]=a[i++]; while(j<=high) r[p++]=a[j++]; for(p=0,i=low;i<=high;p++,i++) a[i]=r[p]; delete [] r;}
歸併排序
歸併排序有兩種實現方法:自底向上和自頂向下。
1、 自底向上的方法 (1)自底向上的基本思想
自底向上的基本思想是:第1趟歸併排序時,將待排序的檔案R[1..n]看作是n個長度為1的有序子檔案,將這些子檔案兩兩歸併,若n為偶數,則得到 個長度為2的有序子檔案;若n為奇數,則最後一個子檔案輪空(不參與歸併)。故本趟歸併完成後,前 個有序子檔案長度為2,但最後一個子檔案長度仍為1;第2趟歸併則是將第1趟歸併所得到的 個有序的子檔案兩兩歸併,如此反覆,直到最後得到一個長度為n的有序檔案為止。
上述的每次歸併操作,均是將兩個有序的子檔案合并成一個有序的子檔案,故稱其為"二路歸併排序"。類似地有k(k>2)路歸併排序。
(2)一趟歸併演算法
分析:
在某趟歸併中,設各子檔案長度為length(最後一個子檔案的長度可能小於length),則歸併前R[1..n]中共有 個有序的子檔案:R[1..length],R[length+1..2length],…, 。
注意:
調用歸併操作將相鄰的一對子檔案進行歸併時,必須對子檔案的個數可能是奇數、以及最後一個子檔案的長度小於length這兩種特殊情況進行特殊處理:
① 若子檔案個數為奇數,則最後一個子檔案無須和其它子檔案歸併(即本趟輪空);
② 若子檔案個數為偶數,則要注意最後一對子檔案中後一子檔案的區間上界是n。
具體演算法如下:
void MergePass(int *a,int n,int length) { int i=0; for(;i+2*length<n-1;i+=2*length) { Merge(a,i,i+length-1,i+2*length-1); } if(i+length<=n-1) Merge(a,i,i+length-1,n-1);//尚有兩個子檔案,其中後一個長度小於length,歸併最後兩個子檔案 //注意:若i≤n-1且i+length-1≥n-1時,則剩餘一個子檔案輪空,無須歸併 }
(3)二路歸併排序演算法
//自底向上 void MergeSort(int *a,int n) { for(int length=1;length<n;length*=2) MergePass(a,n,length); }
注意:
自底向上的歸併排序演算法雖然效率較高,但可讀性較差
2、自頂向下的方法
採用分治法進行自頂向下的演算法設計,形式更為簡潔。
(1)分治法的三個步驟
設歸併排序的當前區間是R[low..high],分治法的三個步驟是:
①分解:將當前區間一分為二,即求分裂點
②求解:遞迴地對兩個子區間R[low..mid]和R[mid+1..high]進行歸併排序;
③組合:將已排序的兩個子區間R[low..mid]和R[mid+1..high]歸併為一個有序的區間R[low..high]。
遞迴的終結條件:子區間長度為1(一個記錄自然有序)。
(2)具體演算法(遞迴實現)
//自頂向下(遞迴實現)void MSort(int *a,int low,int high){ if(low<high){ int mid=(low+high)/2; MSort(a,low,mid); MSort(a,mid+1,high); Merge(a,low,mid,high);}}void MergeSort(int *a,int n){MSort(a,0,n-1);}(3)具體演算法(非遞迴實現)
//將上述遞迴轉化為非遞迴#define Partition '0' //劃分#define Mergetion '1' //歸併typedef struct Region{int first;//起始位置int end; //結束位置char flag;//標記該地區是應該劃分還是應該歸併 }Region;//非遞迴實現(感覺跟二叉樹後序遍曆的非遞迴實現很像)void NonRecursiveMergeSort(int *a,int len){stack<Region> region_stack;Region beginRegion;beginRegion.first=0;beginRegion.end=len-1;beginRegion.flag=Partition;region_stack.push(beginRegion);while(!region_stack.empty()){Region region=region_stack.top();region_stack.pop();//從棧中刪除if(region.flag==Mergetion)//應該歸併{Merge(a,region.first,(region.first+region.end)/2,region.end);//歸併之}else //應該劃分{if(region.first+1>=region.end)//如果地區是兩個相鄰的數{Merge(a,region.first,(region.first+region.end)/2,region.end);//直接合并之}else //否則應該劃分{region.flag=Mergetion;//下次應該歸併region_stack.push(region);int mid=(region.first+region.end)/2;Region region_low;region_low.first=region.first;region_low.end=mid;region_low.flag=Partition;region_stack.push(region_low);Region region_up;region_up.first=mid+1;region_up.end=region.end;region_up.flag=Partition;region_stack.push(region_up);}}}}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////完整代碼展示如下
// 歸併排序(改進版).cpp : 定義控制台應用程式的進入點。#include "stdafx.h"#include <iostream>#include <stack>using namespace std;void printArray(int *a,int n){for(int i=0;i<n;i++)cout<<a[i]<<" ";cout<<endl;}void Merge(int * a,int low,int mid,int high){ int i=low,j=mid+1,p=0; int * r=new int[high-low+1]; while(i<=mid && j<=high) { r[p++]=(a[i]<=a[j])?a[i++]:a[j++]; } while(i<=mid) r[p++]=a[i++]; while(j<=high) r[p++]=a[j++]; for(p=0,i=low;i<=high;p++,i++) a[i]=r[p]; delete [] r;}/* void MergePass(int *a,int n,int length) { int i=0; for(;i+2*length<n-1;i+=2*length) { Merge(a,i,i+length-1,i+2*length-1); } if(i+length<=n-1) Merge(a,i,i+length-1,n-1);//尚有兩個子檔案,其中後一個長度小於length,歸併最後兩個子檔案 //注意:若i≤n-1且i+length-1≥n-1時,則剩餘一個子檔案輪空,無須歸併 } //自底向上 void MergeSort(int *a,int n) { for(int length=1;length<n;length*=2) MergePass(a,n,length); }*/ //自頂向下(遞迴實現)void MSort(int *a,int low,int high){ if(low<high){ int mid=(low+high)/2; MSort(a,low,mid); MSort(a,mid+1,high); Merge(a,low,mid,high);}}void MergeSort(int *a,int n){MSort(a,0,n-1);}//將上述遞迴轉化為非遞迴#define Partition '0' //劃分#define Mergetion '1' //歸併typedef struct Region{int first;//起始位置int end; //結束位置char flag;//標記該地區是應該劃分還是應該歸併 }Region;//非遞迴實現(感覺跟二叉樹後序遍曆的非遞迴實現很像)void NonRecursiveMergeSort(int *a,int len){stack<Region> region_stack;Region beginRegion;beginRegion.first=0;beginRegion.end=len-1;beginRegion.flag=Partition;region_stack.push(beginRegion);while(!region_stack.empty()){Region region=region_stack.top();region_stack.pop();//從棧中刪除if(region.flag==Mergetion)//應該歸併{Merge(a,region.first,(region.first+region.end)/2,region.end);//歸併之}else //應該劃分{if(region.first+1>=region.end)//如果地區是兩個相鄰的數{Merge(a,region.first,(region.first+region.end)/2,region.end);//直接合并之}else //否則應該劃分{region.flag=Mergetion;//下次應該歸併region_stack.push(region);int mid=(region.first+region.end)/2;Region region_low;region_low.first=region.first;region_low.end=mid;region_low.flag=Partition;region_stack.push(region_low);Region region_up;region_up.first=mid+1;region_up.end=region.end;region_up.flag=Partition;region_stack.push(region_up);}}}}int _tmain(int argc, _TCHAR* argv[]){int a[]={49,38,65,97,76,13,27,49};int n=sizeof(a)/sizeof(int);printArray(a,n);//MergeSort(a,n);NonRecursiveMergeSort(a,n);printArray(a,n);system("PAUSE");return 0;}
本文參考:資料結構(c語言 嚴蔚敏版)P283-P284 http://student.zjzk.cn/course_ware/data_structure/web/paixu/paixu8.5.1.1.htm