合并排序是一种分治法,实现上用了递归结构。过程是:先将待排序的元素分为两部分,一般是对等长度的两部分,称为左右L、R,先分别将L,R进行合并排序,然后将排序好的L、R合并在一起,则所有元素都有序。复杂度O(nlgn)。
#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[n]; 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;//只增加这一句便可求逆序数 } } while(i<=mid) tmpArr[cur++]=arr[i++]; 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语言.
对
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。
则调用的顺序大致可以看成下面的图:
开始调用时是(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[n]; 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[n]; 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),并且避免递归也在时间性能上有一定的提升,应该说,使用归并排序时,尽量考虑用非递归方法
上面的迭代方法没有下面的简洁:
/** * 自定向上排序 * * @param arr 待排序数组 * @param n 数组的长度 */ public static void mergeSort(int[] arr, int n) { /* * 数组分2层循环 第一层是确定分组后每个组的长度,按照上面图的图示所知 * size分别为1,2,4,8... * 当size=1 那么arr1.length=1 arr2.length=1 所以 1-1 排序 最终 “每” 2个元素有序 * 当size=2 那么arr1.length=2 arr2.length=2 所以 2-2 排序 最终 “每” 4个元素有序 * 当size=4 那么arr1.length=4 arr2.length=4 所以 4-4 排序 最终 “每” 8个元素有序 * 所以这层循环 就是为了帮助我们创建符合要求变化的size大小 * 最开始数组长度=1 也就是1个元素 他就是有序的 那么size = 2 * size这个算式就帮助我们迭代创建size分别为1,2,4,8... * */ for (int size = 1; size <= n; size = 2 * size) { /* *i表示的是每个要合并分作的起始坐标也就是left 我们知道 left-right是通过mid分成arr1和arr2的 * 也就是[l...mid]-[mid+1...r] 等价于[l...size-1]和[size...r] * 所以i的取值变化为i = i + 2 * size * 同时i不能越界 所以i<n */ for (int i = 0; i + size < n; i = i + 2 * size) { /* * 上面分析[l...mid]-[mid+1...r] 等价于[l...size-1]和[size...r] * 所以 i等价l i + size - 1等价mid i + size + size - 1等价r * 虽然i<n合法 但是i + size可能越界, * 同时当i+size>=n说明 只有arr1 arr2为null,那么也就不归并了 他就是有序的 * 因为从上面得知,归并的前提是arr1和arr2是有序的 * * * i + size + size - 1相当于r 他可能越界 所以Math.min(i + size + size - 1, n - 1) * */ merge(arr, i, i + size - 1, Math.min(i + size + size - 1, n - 1)); } } }
如果不理解上面代码可以看图解:http://book.51cto.com/art/201108/287081.htm
扩展:自然和并排序
自然合并排序是合并排序算法的一种改进。
#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[n]; 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
归并排序优化
public static void mergeSort(int[] arr, int l, int r) {
if (r-l<=15) {
insertSort(arr,l,r);// 1 .
return;
} else {
//找到中间边界mid 拆分2个数组[l...mid]和[mid+1...r]
int mid = (r - l) / 2 + l;
//左边继续拆分
mergeSort(arr, l, mid);
//右边边继续拆分
mergeSort(arr, mid + 1, r);
//一直拆分到l==r 说明只有一个元素 retuen
//然后开始回溯合并排序
if (arr[mid] > arr[mid + 1])//2.
merge(arr, l, mid, r);
}
}
- 当拆分到足够小的时候选择使用插入排序,原始是插入排序对相对有序的数组效率比较高,所以当数组越小的时候有序的几率就越大,所以使用插入排序
- (arr[mid] <= arr[mid + 1]) 也就是所arr1中的最大的元素已经比arr2最小的元素还要小,那么arr1所有元素就小于等于arr2的所有元素,因为再归并中arr1和arr2都是有序的,那么此时[l...r]就是有序的,所以当(arr[mid] > arr[mid + 1])时我们才需要排序
《算法导论》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";
什么是逆序对.逆序对是判断一个数组有序程度的一个标示。一个完全有序的数组,逆序对个数=0 1 2 3 4 5 6 8 7 一个逆序对 利用归并思想,当向上归并: 2 3 6 8 | 1 4 5 7 2与1比较 1<2 那么1比左边2 3 6 8 都要小那么此时逆序对4 继续归并到 1 2 3 当4<6时 4比左边6 8 小 逆序对未2 最终将计数相加
https://leetcode-cn.com/problems/shu-zu-zhong-de-ni-xu-dui-lcof/
https://www.jianshu.com/p/d3b9ffdf1089