归并排序也是分治策略的一种应用,基本思想是:一个已经排序好的数组可以由他的两个已经排序好的子数组得到(依次从两个子数组中拿出较小(或较大,取决于排序方式)的元素放回到父数组中,如果一个数组中的元素已经全部拿刀父数组中去了,就把剩下的元素全部放回到父数组的后面);而排序好的子数组当然也可以用同样的方式得到,也就是说要将待排序的数组不断划分,直到划分成的子数组中只有一个元素为止。
#include<stdio.h>
#include<stdlib.h>
#define random(n) (rand()%n)
#define N 12
#define NOTATION 99999999
//这个做法在每个子数组的后面设置了哨兵,当有效的数组全部被复制会父数组的之后,
//达到了这个哨兵,由于这个哨兵是一个非常大的数字,所以在与另外一个没有复制完成的
//数组进行比较的时候,每次都是将没有复制完成的数组中的元素复制会父数组
//这种做法的优点是不需要每复制一次就判断子数组有没有复制完成
/*
void merge(int *a,int p,int q,int r){
int n1=q-p+1;
int n2=r-q;
int *L=(int *)malloc(sizeof(int)*n1);
int *R=(int *)malloc(sizeof(int)*n2);
int i,j,k;
for(i=0;i<n1;i++){
L[i++]=a[p+i];
i--;
}
for(j=0;j<n2;j++){
R[j++]=a[q+j+1];
j--;
}
L[n1]=NOTATION; //设置哨兵
R[n2]=NOTATION;
i=0;
j=0;
for(k=p;k<=r;k++){
if(L[i]<=R[j]){
a[k]=L[i];
i++;
}
else{
a[k]=R[j];
j++;
}
}
}
*/
//这个做法没有设置哨兵,但是需要在每次复制完成之后判断两个子数组是否已经复制完成,
//如果有一个子数组已经复制完成,那么将另外的一个子数组中的元素全部复制到父数组中
void merge(int *a,int p,int q,int r){
int n1=q-p+1;
int n2=r-q;
int *L=(int *)malloc(sizeof(int)*n1);
int *R=(int *)malloc(sizeof(int)*n2);
int i,j,k;
for(i = 0;i < n1; i++){
L[i++] = a[p+i];
i--;
}
for(j = 0;j < n2; j++){
R[j++] = a[q+j+1];
j--;
}
i=0;
j=0;
for(k = p; k <= r; k++){
if(L[i] <= R[j]){
a[k]=L[i];
i++;
}
else{
a[k] = R[j];
j++;
}
if(i >= n1){ //L已经复制完了
for(k++; j < n2; j++, k++)
a[k] = R[j];
break;
}
else if(j >= n2) { //R已经复制完了
for(k++; i < n1; i++, k++)
a[k] = L[i];
break;
}
}
}
void merge_sort(int *A,int p,int r){
int q;
if(p < r){
q=(p+r)/2;
merge_sort(A, p, q);
merge_sort(A, q+1, r);
merge(A, p, q, r);
}
}
void main(){
int n=0;
int i=0;
int j=0;
int a[N];
printf("排序前:\n");
for(n = 0; n < N; n++){
a[n] = random(100);
printf("%-5d", a[n]);
}
printf("\n");
merge_sort(a, 0, N);
printf("排序后:\n");
for(i=1; i <= N; i++)
printf("%-5d", a[i]);
printf("\n");
}
代码中的merge方法用于将两个已经排序好的子数组合并成一个排序好的数组,上面提供了两种方式,两种方式的区别在注释中有说明。
复杂度分析:
当问题规模为n时,代码中将每个问题都划分为两个规模为n/2的子问题,将问题分解需要的时间为C(n),将问题合并需要的时间为D(n),所以T(n) = 2*T(n/2) + C(n) + D(n),也即T(n) = Ɵ(nlgn)。