归并排序与选择排序相同,归并排序也不受数据具体值的影响,只与数据的数量有关,时间复杂度为T(n)=nlogn且运行时非常稳定,但缺点是会占用额外的使用空间。
归并排序是一个典型的分治法的使用案例。简单来说,是将要排序的序列分成两个子序列,再分别对这两个子序列进行递归,到不能再分的时候进行排序,然后逐级的合并上去。即由多个有序子序列合并成为一个有序主序列。步骤如下:
1.将序列分成两个子序列;
2.对这两个子序列分别进行归并排序;
3.将这两个子序列有序的合并成为一个有序的主序列。
合并:当递归进行到合并这一操作时,我们的子序列只有两种情况,一种是子序列无法再分即只有一个元素,另一种是子序列是一个已经被合并过后的有次序的子序列。这两种情况我们可以看作一种,即在合并时子序列是一个有序的序列。此时两个子序列想要合并成为一个主序列就简单的多了,我们可以循环的判断两个子序列中的元素,并将其中小的那个选出来,直到其中一个子序列中的元素被提取完毕,那么另一个子序列中的元素就可以直接提取到主序列中去了(这个过程要用一个另外的空间来存储这些数据)。在这样递归的完成合并之后,我们最终得到的就是一个有序的主序列了,见图:(图来自于大佬的:算法学习总结(2)——温故十大经典算法-一杯甜酒)
最佳情况:T(n) = O(n) 最差情况:T(n) = O(nlogn) 平均情况:T(n) = O(nlogn)
代码如下:
#include<stdio.h>
#include<stdlib.h>
void merge(int * a,int * temp,int L,int Lend,int Rend){
int tmp=L;//tmp是用于临时记录主序列的下标
int Llend=Lend-1,count=Rend-L+1;//count是指数据元素个数,Llend是指左子序列的终点
int i=L,j=Lend;//i,j是用于记录左子序列和右子序列在合并时的下标
while(i<=Llend&&j<=Rend){
if(a[i]>a[j]){
temp[tmp++]=a[j++];
}
else temp[tmp++]=a[i++];
}
while(i<=Llend)temp[tmp++]=a[i++];//这两个循环是用于对于左序列或者右序列中没有被合并到主序列的元素,直接调入即可
while(j<=Rend)temp[tmp++]=a[j++];
for(i=0;i<count;i++,Rend--){//对原数组进行更新,有了这一步才能保证子序列中的数据永远都是有序的。
a[Rend]=temp[Rend];
}
}
void mergesort(int * a,int * temp,int L,int R){
if(L<R){
int center=(L+R)/2;
mergesort(a,temp,L,center);
mergesort(a,temp,center+1,R);
merge(a,temp,L,center+1,R);
}
}
void sort(int * a,int n){
int * temp;
temp=(int *)malloc(sizeof(int)*(n));
mergesort(a,temp,0,n-1);//temp是用来存放数据的临时空间,数组下标有效数据范围为[0,n-1]
free(temp);
}
int main(){
int * a,i,n;
float p;
scanf("%d",&n);
a=(int *)malloc(sizeof(int)*n);
srand(time(0));
for(i=0;i<n;i++){//用于随机生成数据
p=(float)rand()/RAND_MAX;
p=p*1000+10;
a[i]=p;
}
sort(a,n);;
for(i=0;i<n;i++)printf("%d ",a[i]);
}