排序思路
归并排序就是多次将两个或两个以上的有序表合并成一个新的有序表。最简单的就是直接将两个有序的子表合并成一个有序的表即二路归并。
二路归并思路:将a[0,n-1]看成是n个长度为1的有序子表,然后两两归并,得到个长度为2(最后一个有序序列长度可能为1)的有序序列;再进行两两归并,得到 个长度为4(最后一个有序序列长度可能小于4)的有序序列,……直到得到一个长度为n的有序序列。
注意:归并排序每趟产生的有序区只是局部有序区。
排序算法
首先实现将两个相邻有序子表合并成一个有序序列。这里需要用到一个辅助数组来存放合并过程中的有序区。
对于两个子表中的元素一一比较,较小的放入辅助区。比较结束如果其中一个子表还有元素,直接将其剩余元素复制到辅助区。
代码实现
//将两个连续子表归并为一个有序表
void Merge(int a[],int low,int mid,int high){
int i=low,j=mid+1,k=0,*R;//i为第一个子表开始下标,j为第二个子表开始下标,k为辅助表的开始下标,R为辅助数组
//动态分配辅助数组R空间
R=(int *)malloc((high-low+1)*sizeof(int));
while(i<=mid&&j<=high){//在第一段和第二段均未扫描完时循环
if(a[i]<=a[j]){//将第一段元素放入R中
R[k]=a[i];
i++;k++;
}else{ //将第二段元素放入R中
R[k]=a[j];
j++;k++;
}
}
while(i<=mid){//将第一段剩余元素直接放入R中
R[k]=a[i];
i++;k++;
}
while(j<=high){//将第二段剩余元素直接放入R中
R[k]=a[j];
j++;k++;
}
//将辅助数组R赋值回a中
for(k=0,i=low;i<=high;k++,i++){
a[i]=R[k];
}
free(R);//释放R数组
}
接着实现一趟归并。需要注意的是待归并数列中划分的子表个数可能是奇数或偶数。如果是奇数,最后一个子表不用和其他子表归并。如果是偶数,当最后一个子表的长度小于其他子表长度,需要单独处理。
代码实现
//对整个表进行一趟归并
/*
参数1:待归并数组
参数2:子表长度
参数3:整个表的长度
*/
void MergePass(int a[],int lengh,int n){
int i;
for(i=0;i+2*lengh-1<n;i=i+2*lengh){//归并所有length长的两相邻子表
Merge(a,i,i+lengh-1,i+2*lengh-1);
}
//余下两个子表,后者的长度小于length
if(i+lengh-1<n){
Merge(a,i,i+lengh-1,n-1);//归并这两个子表
}
}
接着利用上面两个模块,直接进行自底向上的二路归并算法
代码实现
//归并函数
void MergeSort(int a[],int n){//自底向上二路归并
int length;
for(length=1;length<n;length=2*length){
MergePass(a,length,n);
}
}
其中for循环进行了趟归并。
完整代码案例
#include <iostream>
#include <cstdlib>
using namespace std;
//将两个连续子表归并为一个有序表
void Merge(int a[],int low,int mid,int high){
int i=low,j=mid+1,k=0,*R;//i为第一个子表开始下标,j为第二个子表开始下标,k为辅助表的开始下标,R为辅助数组
//动态分配辅助数组R空间
R=(int *)malloc((high-low+1)*sizeof(int));
while(i<=mid&&j<=high){//在第一段和第二段均未扫描完时循环
if(a[i]<=a[j]){//将第一段元素放入R中
R[k]=a[i];
i++;k++;
}else{ //将第二段元素放入R中
R[k]=a[j];
j++;k++;
}
}
while(i<=mid){//将第一段剩余元素直接放入R中
R[k]=a[i];
i++;k++;
}
while(j<=high){//将第二段剩余元素直接放入R中
R[k]=a[j];
j++;k++;
}
//将辅助数组R赋值回a中
for(k=0,i=low;i<=high;k++,i++){
a[i]=R[k];
}
free(R);//释放R数组
}
//对整个表进行一趟归并
/*
参数1:待归并数组
参数2:子表长度
参数3:整个表的长度
*/
void MergePass(int a[],int lengh,int n){
int i;
for(i=0;i+2*lengh-1<n;i=i+2*lengh){//归并所有length长的两相邻子表
Merge(a,i,i+lengh-1,i+2*lengh-1);
}
//余下两个子表,后者的长度小于length
if(i+lengh-1<n){
Merge(a,i,i+lengh-1,n-1);//归并这两个子表
}
}
//归并函数
void MergeSort(int a[],int n){//自底向上二路归并
int length;
for(length=1;length<n;length=2*length){
MergePass(a,length,n);
}
}
int main()
{
int a[]={0,9,8,7,6,5,4,3,2,1};
MergeSort(a,10);
for(int i=0;i<10;i++){
cout << a[i] << endl;
}
return 0;
}
算法分析
对于长度为n的表,需要进行趟二路归并。每趟归并时间为O(n)
时间复杂度最好情况和最坏情况均是:
归并排序中每一次需要需要一个辅助向量来保存两个有序子表归并结果,该次结束后释放其空间。所以
空间复杂度为O(n)
归并排序是一种稳定排序