写了好久,学到给个赞把 图片来自网络,侵删
我在学习归并排序的时候经常看到分治法,先“分”后“治”。其实分就是把数组看成一个一个的数字,代码里面没有这个东西。我刚学的时候还纠结了很久。
话不多说,直接解剖算法。
首先我们拿到一个数组,题目叫我们归并排序,你第一个想法肯定是我要怎么做第一步嘛 诶,我们看到这张图片,它是把每两个都合在一起了,合在一起后还是两两有序的,那怎么操作呢,直接for一遍,每次跳两格,然后直接if(a[j]>a[j+1]) a[j]<=>a[j+1];(换位)不就可以了吗,嗯,第一步看起来已经完成了 我们来看第二步 第二步这里是把刚刚合并好的两两的数组合并成一个更大的四四有序的数组 刚才的方法试试还能不能用,干脆for一遍,每次跳四个 可是我们刚刚是if(a[j]>a[j+1]) a[j]<=>a[j+1];(换位)这样排好序的,那怎么把四个排好,emmmmm,要不冒泡一遍吧,for一遍,每次跳四个,for里面就写冒泡,不过,这样。。。好像for三层了,O(n^3)了,OJ估计要超时了,不能这么写。 那怎么办 诶,我们学过两个有序表的合并啊,复杂度只有O(n),刚好我刚刚弄出来的两两有序的数组也是有序的,这么多的机缘巧合,都指向了我们要用有序表的合并,对,就是它了。两两合并,那就这样
int i=1,j=3,k=1; //j=3表示j代表第二个两两有序的数组的第一个数字,也就是图里的5 while(i<=2&&j<=4) { if(a[i]<a[j]) T[k++]=a[i++]; else T[k++]=a[j++]; } while(i<=mid) T[k++]=a[i++]; while(j<=high) T[k++]=a[j++]; for(i=1,j=1;i<=4;i++,j++) a[i]=T[j]; //零时数组里又导回原来的数组写到这里,我们看到它只能把前两个两两有序的数组合并,后面两个两两有序的咋办?for一遍改一下i,j的参数,好像行得通。就是在有序表合并函数头上加一个 for(i=1,j=3;j+1!=n;j+=2,i+=2)。完美。
第二步都解决了,那第三步模仿着来就行了。 四四有序的数组合并成八个的,刚才用的有序表好像也行得通,那就试试? //改i j的参数肯定是行不通了,数组规模都变了
int i=1,j=5,k=1; //j=5表示j代表第二个两两有序的数组的第一个数字,也就是图里的1 while(i<=4&&j<=8) { if(a[i]<a[j]) T[k++]=a[i++]; else T[k++]=a[j++]; } while(i<=mid) T[k++]=a[i++]; while(j<=high) T[k++]=a[j++]; for(i=1,j=1;i<=8;i++,j++) a[i]=T[j]; //零时数组里又导回原来的数组又解决的,我们真是聪明。 八八合并一样解决就行了。 等一下,那如果是128合128那不是for要写到歇菜了 不行,这个想法真不好,有没有办法把有序表的合并写到一个函数里,直接改传入参数就可以一直用一个函数了, 答案肯定是可以的,这就是我们Merge函数的由来
void Merge (int a[],int low,int mid,int high) { int i=low,j=mid+1,k=1; while(i<=mid&&j<=high) { if(a[i]<a[j]) T[k++]=a[i++]; else T[k++]=a[j++]; } while(i<=mid) T[k++]=a[i++]; while(j<=high) T[k++]=a[j++]; for(i=low,j=1;i<=high;i++,j++) a[i]=T[j];//把排好序的数组复制到本身 }看这个函数,我们要两两合并的时候就传入low=1,high=low+4-1,mid=(low+high)/2 然后再让low+=4,搞定 四四合并呢? low=1,high=low+8-1,mid=(low+high)/2 然后再让low+=8 ,又搞定了。
看到我加粗那几个数字了吗,有没有发现他们很有特点,两两合并的时候是4 ,四四合并的时候是8 ,刚好就是 几几合并中的“几x2”,就是我们合并前每个有序数组的长度x2,不如就叫我们合并前每个有序数组的长度step吧,表示我这一轮是几个数字和几个数字合并,step就这么产生了。
写到这里,无论是几几合并都不是问题了,一 一合并就for一遍也过了。但是,我惊奇的 发现一 一合并竟然也可以用有序表的合并来写low=1,high=low+2-1,mid=(low+high)/2 然后再让low+=2,仔细想想,原来两个数也可以进行有序表的合并的哦,那真是又省了很多行代码,真爽
原理就这样讲完了,是不是很简单易懂啊?当然——不,还没讲完呢
说了四四合并,八八合并,那,假如,我有十个数字怎么办,四四合并后边两个就晾着吗?? 不同的地方有不同的写法 我这是OJ题目8645上给的输入样例 输入样例 10 5 4 8 0 9 3 2 6 7 1
输出样例 第一次排序:4 5 0 8 3 9 2 6 1 7 第二次排序:0 4 5 8 2 3 6 9 1 7 第三次排序:0 2 3 4 5 6 8 9 1 7 第四次排序:0 1 2 3 4 5 6 7 8 9 根据题目的意思,就是一 一合并的时候全部都排序,看后边的71 成 17了 再看第二三趟,17就晾在那没有动过,就是前面的排完后面没东西排就不管它就行 最后一趟再把17当成一个有序表和前面的大有序表合并
if(2*step>n) Merge(a,1,step,n);//八八合并step=8,2*8>10,条件成立,排序到这一步,才算是真正完了。 看完觉得自己会一定不是会了,自己不看任何教材自己打一遍过了才叫会了,学习需要时间,慢慢想,慢慢打,一个上午就能掌握了
附上完整代码
#include<stdio.h> int T[100]; int len; void Merge (int a[],int low,int mid,int high) { int i=low,j=mid+1,k=1; while(i<=mid&&j<=high) { if(a[i]<a[j]) T[k++]=a[i++]; else T[k++]=a[j++]; } while(i<=mid) T[k++]=a[i++]; while(j<=high) T[k++]=a[j++]; for(i=low,j=1;i<=high;i++,j++) a[i]=T[j];//把排好序的数组复制到本身 } void Mergesort(int a[],int n) { int step=1;//step=1一 一归并 =2 两两归并 int i,j; while(step<n) { for(i=1;i<=n&&(i+step*2-1<=n);i+=step*2) { int low=i,high=low+step*2-1,mid=(low+high)/2; Merge(a,low,mid,high); }//这个for 循环归并普通情况 比如10个数,归并前8个(1→2→4→8) if(2*step>n) Merge(a,1,step,n);// 一般情况归并完就归并还没归并的 此时10个数 step=8 step*=2; for(j=1;j<=n;j++) printf("%d ",a[j]); printf("\n"); //输出每趟排序的结果 } } main() { int n,a[100],i; scanf("%d",&n);len=n; for(i=1;i<=n;i++) scanf("%d",&a[i]); Mergesort(a,n); }