归并(Merge)排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个有序的子序列,再把有序的子序列合并为整体有序序列。归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
归并过程:
例如有两个有序表:(7,10,13,15)和(4,8,19,20),归并后得到的有序表为:(4,7,8,10,13,15,19,20)。
归并过程为:比较a[i]和a[j]的大小,若a[i]≤a[j],则将第一个有序表中的元素a[i]复制到r[k]中,并令i和k分别加上1,如此循环下去,直到其中一个有序表取完,然后再将另一个有序表中剩余的元素复制到r中从下标k到下标t的单元。
但是上面这个思路的前提是已经有了两个有序的子表,但是在实际排序过程中往往不可能已经有了两个有序子表,所以最好的办法就是先假设子表的长度都为1,因为一个元素总是有序的,然后通过归并得到更长的子表,依次类推,最终得到一个整体的有序序列。
程序代码如下:
归并过程:
例如有两个有序表:(7,10,13,15)和(4,8,19,20),归并后得到的有序表为:(4,7,8,10,13,15,19,20)。
归并过程为:比较a[i]和a[j]的大小,若a[i]≤a[j],则将第一个有序表中的元素a[i]复制到r[k]中,并令i和k分别加上1,如此循环下去,直到其中一个有序表取完,然后再将另一个有序表中剩余的元素复制到r中从下标k到下标t的单元。
但是上面这个思路的前提是已经有了两个有序的子表,但是在实际排序过程中往往不可能已经有了两个有序子表,所以最好的办法就是先假设子表的长度都为1,因为一个元素总是有序的,然后通过归并得到更长的子表,依次类推,最终得到一个整体的有序序列。
程序代码如下:
#include <stdio.h>
#define N 17
void print(int a[], int n)
{
int j;
for(j=0; j < n; j++)
{
if(a[j])
printf("%2d ", a[j]);
}
printf("\n");
}
/*将src[i…m]和src[m +1 …n]归并到辅助数组des[i…n]*/
void two_merge(int *src, int *des, int i, int m, int n)
{
int j, k;
for(j=m+1, k=i; i<=m && j<=n ; k++)
{
if(src[j] < src[i])
des[k] = src[j++];
else
des[k] = src[i++];
}
while(i <= m)
des[k++] = src[i++];
while(j <= n)
des[k++] = src[j++];
printf("合并结果: ");
print(des, N);
}
void merge_sort(int *s, int *d, int length)
{
int step = 1;
int *tmp, *src = s, *des = d;
int i;
/*先将src[0],src[1]合并为src1,src[2],src[3]合并为src2...,再将src1,src2合并...*/
while(step < length)
{
i = 0;
step *= 2; /*step的起始步长应该为2*/
while(i+step <= length) /*length/step能够整除的部分先合并*/
{
two_merge(src, des, i, i+step/2-1, i+step-1);
i = i + step;
}
if((i+step/2) < length) /*i==length表示刚好整除*/
/*除不够且能够合并的在这里合并*/
/*不能合并的状态举例:以step=4合并但是只余了<=2个元素*/
{
two_merge(src, des, i, i+step/2-1, length-1); /*i+step/2-1>length-1的时候two_merge函数会排错*/
}
tmp = src;
src = des;
des = tmp; /*交换src和des数组*/
}
}
int main()
{
int src[N] = {1, 3, 7, 6, 8, 10, 2, 5, 4, 9, 12, 11, 13, 17, 15, 14, 16};
int des[N] = {0};
merge_sort(src, des, N);
printf("排序之后的src:");
print(src, N);
printf("排序之后的des:");
print(des, N);
return 0;
}
程序运行结果:
N=13时
N=17时
分析:从程序输出的结果看,排好序的结果可能最终存放于src数组,也可能最终存放于des数组,这是因为在归并排序的时候,不停的再交换src和des数组,即把一个的合并结果放到另一个数组,另一个的合并结果又反过来放到当前的数组,如此交替,如果需要制定最终结果放到哪个数组,那么可以在merge_sort函数的末尾判断s与src的关系,再进行一次数据复制即可。
归并排序的特点:
时间复杂度:O(nlog(2^n)),因为对于归并排序来说,一个元素比较log(2^n)就会放到最终的位置上,一共有n个元素。
空间复杂度:O(n),用到了一个辅助数组。
稳定性:稳定。