您的位置:首页 > 其它

归并排序(递归和非递归)和自然合并排序

2012-04-29 17:06 399 查看
合并排序是一种分治法,实现上用了递归结构。过程是:先将待排序的元素分为两部分,一般是对等长度的两部分,称为左右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
;
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语言.





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
;
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";



                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: