归并排序(递归和非递归)和自然合并排序
2012-04-29 17:06
399 查看
合并排序是一种分治法,实现上用了递归结构。过程是:先将待排序的元素分为两部分,一般是对等长度的两部分,称为左右L、R,先分别将L,R进行合并排序,然后将排序好的L、R合并在一起,则所有元素都有序。复杂度O(nlgn)。
上面的merge程序要开辟2个数组,有点不好,一个更好的方案:
INT_MAX头文件是在<limits.h>中,这里不要也没问题。
C中int类型32位,范围是-2147483648到2147483647.
(1)最轻微的上溢是 INT_MAX + 1 :结果是 INT_MIN。
(2)最严重的上溢是 INT_MAX + INT_MAX :结果是 -2。
(3)最轻微的下溢是 INT_MIN - 1 :结果是 INT_MAX。
(4)最严重的下溢是 INT_MIN +INT_MIN :结果是 0。
应付溢出的最佳方法就是防范于未然:充分了解数据的范围,选择恰当的变量类型。
也可以考虑改用不需要你担心整数类型溢出的语言--Python语言.
![](http://images.cnitblog.com/blog/277239/201308/18144456-f3c5b3e4333f453aa6cbf858c3bf6c42.jpg)
对
![](http://images.cnitblog.com/blog/277239/201308/18151551-c8fe93bd95d84f0d8e8e70e7df0bf8ef.jpg)
开始调用时是(0,6),然后a(0,3);这时a(0,3)会不断调用自身,到a(0,1)和b[1,1]完成后运行merge(0,1,1).
我们可以想象单独调用(0,1)和(1,1)然后合并(0,1,1). a(0,3)只完成了a(0,1)(即:把a[0]和a[1]合并),还没有完成b(2,3),类似算出结果。
即把a[2]和a[3]合并。然后在merge(0,1,3).
其余类似。
可以参考:
http://www.ituring.com.cn/article/1327
对于算法mergeSort,还可以从多方面对他进行改进。例如,从分治策略的机制入手,容易消除算法中的递归。事实上,mergeSort的递归过程只是将待排序集合一分为2,直至待排序集合只剩下一个元素为止,然后不断合并2个已排好序的数组段。按此机制,可以首先将数组a中相邻元素两两配对,用合并算法将他们排序,构成n/2组长度为2的排好序的子数组段,然后再将他们排序成长度为4的排好序的子数组段,如此继续下去,直至整个数组排好序。
非递归的迭代方法,避免了递归时深度为log2n的栈空间,空间只是用到申请归并临时用的TR数组,因此空间复杂度为O(n),并且避免递归也在时间性能上有一定的提升,应该说,使用归并排序时,尽量考虑用非递归方法
如果不理解上面代码可以看图解:http://book.51cto.com/art/201108/287081.htm
扩展:自然和并排序
自然合并排序是合并排序算法的一种改进。
自然合并排序:对于初始给定的数组,通常存在多个长度大于1的已自然排好序的子数组段.例如,若数组a中元素为{4,8,3,7,1,5,6,2},则自然排好序的子数组段有{4,8},{3,7},{1,5,6},{2}.用一次对数组a的线性扫描就足以找出所有这些排好序的子数组段.然后将相邻的排好序的子数组段两两合并,构成更大的排好序的子数组段({3,4,7,8},{1,2,5,6}).继续合并相邻排好序的子数组段,直至整个数组已排好序。
总结:自然归并排序思想简单,但是实现的时候还有很多细节需要考虑,比如需要归并次数是分组次数除以二取下限。还有就是r的控制,每次都要保证在v.size()-1范围内不能超出。实现时候可能需要不断测试,最终成功。
参考网站:http://blog.163.com/guchonglin-6/blog/static/5752753120099247170200/
输入数据:元素个数n,和x,数组a
输出数据:如果存在两个数则输出那两个数,否则输出“No Answer!”
思想:先归并排序后,在从两端计算:
#include<iostream> using namespace std; void merge(int a[],int p,int q,int r) { int n1=q-p+1; int n2=r-q; //create arrays L[1..n1+1] and R[1..n2+1] int *L=new int[n1+1]; //a[p,q] int *R=new int[n2+1]; //a[q+1,r] for(int i=0;i<n1;i++) L[i]=a[p+i-1]; for(int j=0;j<n2;j++) R[j]=a[q+j]; L[n1]=R[n2]=INT_MAX; int i,j; i=j=0; for(int k=p;k<=r;k++) { if(L[i]<=R[j]) { a[k]=L[i]; i++; } else { a[k]=R[j]; j++; } } } void mergeSort(int a[],int p,int r) { int q; if(p<r) { q=(p+r)/2; mergeSort(a,p,q); mergeSort(a,q+1,r); merge(a,p,q,r); } } int main() { int n; cin>>n; int *a=new int ; for(int i=0;i<n;i++) cin>>a[i]; mergeSort(a,0,n-1); for(int i=0;i<n;i++) cout<<a[i]<<ends; }
上面的merge程序要开辟2个数组,有点不好,一个更好的方案:
void merge(int arr[],int first,int mid,int last) { int *tmpArr=new int[last-first+1]; int i=first,j=mid+1; int cur=0; while(i<=mid && j<=last) { if(arr[i]<arr[j]) { tmpArr[cur++]=arr[i++]; } else { tmpArr[cur++]=arr[j++]; count += mid-i+1;//只增加这一句便可求逆序数 } } if(i<=mid) { while(i<=mid) tmpArr[cur++]=arr[i++]; } else { while(j<=last) tmpArr[cur++]=arr[j++]; } for(int k=0;k<cur;k++) { arr[first++]=tmpArr[k]; } delete[] tmpArr; tmpArr=NULL; }
INT_MAX头文件是在<limits.h>中,这里不要也没问题。
C中int类型32位,范围是-2147483648到2147483647.
(1)最轻微的上溢是 INT_MAX + 1 :结果是 INT_MIN。
(2)最严重的上溢是 INT_MAX + INT_MAX :结果是 -2。
(3)最轻微的下溢是 INT_MIN - 1 :结果是 INT_MAX。
(4)最严重的下溢是 INT_MIN +INT_MIN :结果是 0。
应付溢出的最佳方法就是防范于未然:充分了解数据的范围,选择恰当的变量类型。
也可以考虑改用不需要你担心整数类型溢出的语言--Python语言.
![](http://images.cnitblog.com/blog/277239/201308/18144456-f3c5b3e4333f453aa6cbf858c3bf6c42.jpg)
对
void mergeSort(int a[],int p,int r) { int q; if(p<r) { q=(p+r)/2; mergeSort(a,p,q); mergeSort(a,q+1,r); merge(a,p,q,r); } }怎么理解? 其实,向这种递归调用里有2次递归调用的,我们可以只看一种,另一种一种, 我们另第一句
mergeSort(a,p,q); 为a 第二句
mergeSort(a,p,q); 为b。 则调用的顺序大致可以看成下面的图:
![](http://images.cnitblog.com/blog/277239/201308/18151551-c8fe93bd95d84f0d8e8e70e7df0bf8ef.jpg)
开始调用时是(0,6),然后a(0,3);这时a(0,3)会不断调用自身,到a(0,1)和b[1,1]完成后运行merge(0,1,1).
我们可以想象单独调用(0,1)和(1,1)然后合并(0,1,1). a(0,3)只完成了a(0,1)(即:把a[0]和a[1]合并),还没有完成b(2,3),类似算出结果。
即把a[2]和a[3]合并。然后在merge(0,1,3).
其余类似。
可以参考:
http://www.ituring.com.cn/article/1327
对于算法mergeSort,还可以从多方面对他进行改进。例如,从分治策略的机制入手,容易消除算法中的递归。事实上,mergeSort的递归过程只是将待排序集合一分为2,直至待排序集合只剩下一个元素为止,然后不断合并2个已排好序的数组段。按此机制,可以首先将数组a中相邻元素两两配对,用合并算法将他们排序,构成n/2组长度为2的排好序的子数组段,然后再将他们排序成长度为4的排好序的子数组段,如此继续下去,直至整个数组排好序。
#include<iostream> #include<limits.h> using namespace std; void mergePass(int x[],int y[],int s,int n); void merge(int x[],int y[],int p,int q,int r); void mergeSort(int a[],int n) { int *b=new int ; int s=1; while(s<n) { mergePass(a,b,s,n);//合并到数组b s=s*2; mergePass(b,a,s,n);//合并到数组a s=s*2; } } void mergePass(int x[],int y[],int s,int n) { //合并大小为s的相邻子数组 到y[] int i=0; while(i<=n-2*s) { merge(x,y,i,i+s-1,i+2*s-1); i=i+2*s; } //剩下的元素少于2s if(i+s<n) merge(x,y,i,i+s-1,n-1); else for(int j=i;j<=n-1;j++) y[j]=x[j]; } /*合并s[p:q]和s[q+1,r]到 d[p:r] */ void merge(int s[],int d[],int p,int q,int r) { int i=p,j=q+1,k=p; while((i<=q) && (j<=r)) { if(s[i]<=s[j]) d[k++]=s[i++]; else d[k++]=s[j++]; } if(i>q) for(int m=j;m<=r;m++) d[k++]=s[m]; else for(int m=i;m<=p;m++) d[k++]=s[m]; } int main() { cout<<"输入元素的个数"; int n; cin>>n; int *a=new int ; cout<<"输入"<<n<<"个元素"<<endl; for(int i=0;i<n;i++) { cin>>a[i]; } mergeSort(a,n); cout<<"mergeSort后"<<endl; for(int i=0;i<n;i++) cout<<a[i]<<ends; cout<<endl; }
非递归的迭代方法,避免了递归时深度为log2n的栈空间,空间只是用到申请归并临时用的TR数组,因此空间复杂度为O(n),并且避免递归也在时间性能上有一定的提升,应该说,使用归并排序时,尽量考虑用非递归方法
如果不理解上面代码可以看图解:http://book.51cto.com/art/201108/287081.htm
扩展:自然和并排序
自然合并排序是合并排序算法的一种改进。
自然合并排序:对于初始给定的数组,通常存在多个长度大于1的已自然排好序的子数组段.例如,若数组a中元素为{4,8,3,7,1,5,6,2},则自然排好序的子数组段有{4,8},{3,7},{1,5,6},{2}.用一次对数组a的线性扫描就足以找出所有这些排好序的子数组段.然后将相邻的排好序的子数组段两两合并,构成更大的排好序的子数组段({3,4,7,8},{1,2,5,6}).继续合并相邻排好序的子数组段,直至整个数组已排好序。
#include<iostream> using namespace std; #include<vector> void merge(int a[],int p,int q,int r) { //L1[p,q]大小为q-p+1 //L2[q+1,r]大小为r-q; int n1=q-p+1; int n2=r-q; int *L1=new int[n1+1]; int *L2=new int[n2+1]; for(int i=0;i<n1;i++) L1[i]=a[p+i]; for(int i=0;i<n2;i++) L2[i]=a[q+1+i]; L1[n1]=INT_MAX; L2[n2]=INT_MAX; int i=0,j=0; for(int k=p;k<=r;k++) { if(L1[i]<L2[j]) { a[k]=L1[i]; i++; } else { a[k]=L2[j]; j++; } } delete[] L1; delete[] L2; } void naturalMergeSort(int a[],int n) { vector<int> v; v.push_back(0); //首作为分割点 for(int i=0;i<n-1;i++) //中间的分割点 if(a[i]>a[i+1]) v.push_back(i); v.push_back(n-1); //尾分割点 for(int j=0;j<v.size();j++) //输出测试分割点是否正确,可注释掉 cout<<v[j]<<endl; cout<<v.size()<<"ok"<<endl; int s=1; for(int group=v.size()-1;group!=1;group=(group%2==0?group/2:group/2+1)) { int count=group/2; //合并次数 例如:5组合并需要两次,4组合并两次 //进行第一次合并 int p,q,r; p=0;q=s;r=2*s; if(r>v.size()-1) r=v.size()-1; merge(a,v[p],v[q],v[r]); //进行接下来的合并 for(int j=1;j<count;j++) { p=r;q=p+s;r=q+s; if(r>v.size()-1) r=v.size()-1; merge(a, v[p]+1, v[q],v[r]); } s+=s; } } int main() { int n; cin>>n; int *a=new int ; cout<<"输入"<<n<<"个元素"<<endl; for(int i=0;i<n;i++) { cin>>a[i]; } naturalMergeSort(a,n); for(int i=0;i<n;i++) cout<<a[i]<<endl; return 0; }
总结:自然归并排序思想简单,但是实现的时候还有很多细节需要考虑,比如需要归并次数是分组次数除以二取下限。还有就是r的控制,每次都要保证在v.size()-1范围内不能超出。实现时候可能需要不断测试,最终成功。
参考网站:http://blog.163.com/guchonglin-6/blog/static/5752753120099247170200/
跟多; http://www.cnblogs.com/liushang0419/archive/2011/09/19/2181476.html http://dsqiu.iteye.com/blog/1707111
《算法导论》2.3-7 判定是否存在两数字和为x
问题描述:输入n个数,求给定数组中是否存在两个数,他们的和为x输入数据:元素个数n,和x,数组a
输出数据:如果存在两个数则输出那两个数,否则输出“No Answer!”
思想:先归并排序后,在从两端计算:
bool find(int a[],int len,int x,int &x1,int &x2) { int i,j; for(i=0,j=len-1;i<len;) { int sum=a[i]+a[j]; if(sum==x) { x1=a[i]; x2=a[j]; return true; } else if(sum>x) { j--; } else { i++; } } return false; }
mergeSort(a,0,n-1); int x1, x2; for(int i=0;i<n;i++) cout<<a[i]<<ends; if(find(a,n,15,x1,x2)) { cout<<x1<<ends<<x2<<endl; } else cout<<"no find";
相关文章推荐
- 面试之路(29)-合并两个排序的链表(递归和非递归)
- 面试之路(29)-合并两个排序的链表(递归和非递归)
- 面试之路(29)-合并两个排序的链表(递归和非递归)
- 归并排序、自然排序(递归、分治)
- 面试之路(29)-合并两个排序的链表(递归和非递归)
- 数据结构--排序之归并排序(分治 递归 合并典型案例)
- 合并排序,递归与非递归版本 ,自然全并排序
- 剑指Offer面试题16反转链表(递归和非递归),面试题17合并两个排序的链表(递归)
- 递归树计算合并排序代价(4.2)
- 堆排序、快速排序(递归与非递归)、归并排序效率比较
- 快速排序的递归和非递归分析
- 链表面试题之合并有序的两个线性表-递归和非递归的方法
- 快速排序实现之递归与非递归
- C/C++排序之二(直接插入排序、 折半插入排序、归并排序(递归))
- C#实现归并排序(递归,非递归,自然归并)
- 快速排序、归并排序的递归、非递归实现
- 快速排序递归与非递归
- 递归与分治-合并排序、快速排序以及循环赛问题
- 排序算法之2路归并排序(递归和非递归)
- 归并排序(递归与非递归)