归并排序是一个很稳定的排序方法,它具有以下特点: 1.时间复杂度为θ(NlogN)(关于时间复杂度的符号表示可以查看《算法导论》第一部分第三/四章),稳定而且是比较排序模型中能达到的最快(当然就算换个很舒服的输入也只能花同样的时间) 2.它不是一个in place的排序算法。也就是说它的排序需要借助一个临时数组来存放数据。
归并排序用的是分治法 众所周知(强行众所周知)分治法的三个步骤:拆分(母问题为子问题)-解决(子问题)-合并(子问题的解)。归并排序的这三个步骤描述如下: 拆分:将数组拆为两个子数组(尽量等分) 解决:将两个子数组分别排序 合并:此处是归并排序的重点部分,其思路是这样的——首先创建一个足以容纳下两个数组的所有元素的数列用于储存数据,然后对于两个已经排好序的数列来说,比较其首元素,取小的一方填入新数列,随后将新数列和刚处理的数列指针后移,然后如此往复,当某一个指针已经指向数组最后一个元素之后时,将另一个数组中的元素按顺序填入新数组。 不难看出,如果两子数列已经有序那么最后合并所得的数列一定是有序的。而当问题被递归式地拆分到底(bottom out)时,也就是只有一个元素之后,合并所做的只是将两个元素按照大小顺序填入一个新的只会有两个元素的数组。
这里就只说C啦,C++的也可以按着写反正用到的还没有那么多不同。 首先关于临时数组我是这么想的:既然每次都会用到对应大小的数组,那么干脆就申请一个等大的数组用于排列,正好序号还能对得上。不过考虑到每次排序完成后是临时数组变得有序,所以在排序之后还应有个复制的过程。 因为排序的时候有排序和合并两个操作,所以要有两个函数。因为是自己用所以也不用关心越界/错误情况,所以返回值用void就好
#include <stdio.h>void mergesort(int *arr,int *tem,int begin,int end);void merge(int *arr,int *tem,int begin,int mid,int end);int main()//再次强调:你可以只写main()但一定不要写 void main()!{ int n,i; scanf("%d",&n); int arr[n],tem[n];//C99之后可以用变量作为容量申请数组了 mergesort(arr,tem,0,n-1); reutnr 0;}主函数就完成了,现在我们开始考虑mergesort函数 传入以后要判断是不是已经到底了,如果到底我们是需要一个复制的过程,之后会需要递归排序,最后用合并函数。 于是mergesort函数写成这样:
void mergesort(int *arr,int *tem,int begin,int end){ if(begin<end){ int mid=(begin+end)>>1; mergesort(arr,tem,begin,mid); mergesort(arr,tem,mid+1,end); merge(arr,tem,begin,mid,end); }else{ tem[begin]=arr[begin]; }}现在可以开始merge函数的编写了,这也是我认为的归并排序最难的地方,我理出来的思路是这样的: 因为最后是有一个复制的过程的,所以指针在开始的时候是不能变的,那么就在开始申请变量赋值用于开始的排序就好。 int *p1=tem+first,*p2=tem+mid+1,*p3=arr+first; 对于是否满足放入数组的条件我一开始写成这样:
if((p1<tem+mid+1)&&(*p1<*p2))错在哪里? 注意如果p2都已经到末尾了(越界),这个坏东西还是会判断后面的垃圾数据是不是满足条件,而我们又都知道C语言的逻辑是存在短路这个东西的,所以最后merge函数应该长这样:
void merge(int *arr,int *tem,int first,int mid,int last){ int *p1=tem+first,*p2=tem+mid+1,*p3=arr+first; int i; for(i=0;i<last-first+1;i++){ if((p2>tem+last)||(p1<tem+mid+1)&&(*p1<*p2)){ *(p3++)=*(p1++); }else{ *(p3++)=*(p2++); } }while(first<=last){ *(tem+first)=*(arr+first);//可别忘复制这个操作 first++; }}如果感觉自己明白了,不妨动手试一试写一个自己的归并排序吧~新闻热点
疑难解答