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