您的位置:首页 > 其它

最大连续子序列问题的研究

2016-03-23 20:17 441 查看
最大连续子序列,即从一个序列中选取一段连续的子序列,使这个子序列的和最大,例如0 6 -1 1 -6 7 -5 最大和为7,从第一个数到第6个数,最大子序列有动态规划法和分治法两种,首先讨论分治法,对一个序列而言,最大连续子序列要么在左边的序列中,要么在右边的序列中(从中间断开),要么在左右各一部分,显然,从这个角度想,我们将问题划分为了两个子问题,从左边找最大连续子序列或者从右边找连续子序列,如果在中间,那么我们只需要暴力遍历,左边从mid到left,右边从mid+1到right,

显然,T(n)=T(n/2)*2+n,其中n代表最坏情况下最大子序列在中间,而且从left到right,复杂度为O(nlog2n)以下是我实现的代码。

<span style="font-family:Courier New;">int *maxsum(int a[],int left,int right) {
int *m=new int[3]; //m[0]记录最大连续子序列起始位置,m[1]记录终止位置,m[2]表示最大和
if(left==right) { // 返回条件
m[0]=m[1]=left;
m[2]=a[left]; //cout<<m[0]<<" "<<m[1]<<" "<<m[2]<<endl;
return m;
}
int mid=(left+right)/2;
int *l=maxsum(a,left,mid);
int *r=maxsum(a,mid+1,right); //左右递归,划分子问题
int leftsum=a[mid],maxleft=a[mid]; //暴力求解maxleft
m[0]=mid;
for(int i=mid-1;i>=left;i--) {
leftsum+=a[i];
if(leftsum>=maxleft) {
maxleft=leftsum; m[0]=i;
}
}

int rightsum=a[mid+1],maxright=a[mid+1];</span><span style="font-family: 'Courier New';">//暴力求解maxright</span><span style="font-family:Courier New;">

m[1]=mid+1;
for(int i=mid+2;i<=right;i++) {
rightsum+=a[i];
if(rightsum>=maxright) {
maxright=rightsum; m[1]=i;
}
}
m[2]=maxleft+maxright;
if(l[2]>=m[2] && l[2]>=r[2]) { //防止内存泄露,及时处理new的内存
delete m; delete r; //cout<<l[0]<<" "<<l[1]<<" "<<l[2]<<endl;
return l;
}
else if(m[2]>=l[2] && m[2]>=r[2]) {
delete l; delete r; //cout<<m[0]<<" "<<m[1]<<" "<<m[2]<<endl;
return m;
}
else if(r[2]>=m[2] && r[2]>=l[2]) {
delete m; delete l; //cout<<r[0]<<" "<<r[1]<<" "<<r[2]<<endl;
return r;
}
}</span>
以下是杭电1003的分治法求解实现,使用上述函数

<span style="font-family:Courier New;">#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int maxn=100000+5;
int t[maxn];

int *maxsum(int a[],int left,int right) {
int *m=new int[3];
if(left==right) {
m[0]=m[1]=left;
m[2]=a[left]; //cout<<m[0]<<" "<<m[1]<<" "<<m[2]<<endl;
return m;
}
int mid=(left+right)/2;
int *l=maxsum(a,left,mid);
int *r=maxsum(a,mid+1,right);
int leftsum=a[mid],maxleft=a[mid];
m[0]=mid;
for(int i=mid-1;i>=left;i--) {
leftsum+=a[i];
if(leftsum>=maxleft) {
maxleft=leftsum; m[0]=i;
}
}

int rightsum=a[mid+1],maxright=a[mid+1];
m[1]=mid+1;
for(int i=mid+2;i<=right;i++) {
rightsum+=a[i];
if(rightsum>=maxright) {
maxright=rightsum; m[1]=i;
}
}
m[2]=maxleft+maxright;
if(l[2]>=m[2] && l[2]>=r[2]) {
delete m; delete r; //cout<<l[0]<<" "<<l[1]<<" "<<l[2]<<endl;
return l;
}
else if(m[2]>=l[2] && m[2]>=r[2]) {
delete l; delete r; //cout<<m[0]<<" "<<m[1]<<" "<<m[2]<<endl;
return m;
}
else if(r[2]>=m[2] && r[2]>=l[2]) {
delete m; delete l; //cout<<r[0]<<" "<<r[1]<<" "<<r[2]<<endl;
return r;
}
}
int main() {
int T;
cin>>T;
for(int ca=1;ca<=T;ca++ ){
int n;
cin>>n;
for(int i=0;i<n;i++) {
cin>>t[i];
}
int *p=maxsum(t,0,n-1);
cout<<"Case "<<ca<<":"<<endl;
cout<<p[2]<<" "<<p[0]+1<<" "<<p[1]+1<<endl;
delete p;
if(ca<T) cout<<endl;
}
return 0;
}</span>


2,尝试使用动态规划法求解,对我而言真的很不显然,以下标 i 结尾的序列的最大连续字段和 maxsum[i] = ( maxsum[i-1] , 0) +a[i] (其实就是判断前n-1个最大字段和是否大于0 ,如果大于,显然加上前面的,否则,要自己就够了) 显然复杂度为O(n),根据maxsum的值,暴力向前,暴力向后,也能找到起始和结束,以下是实现的代码,还是上面的一题hdu1003

<span style="font-family:Courier New;">#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
using namespace std;

const int maxLen=100000+5;
int a[maxLen];
int maxn[maxLen];
int main()
{
int T;
scanf("%d",&T);
for(int time=1;time<=T;time++)
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
maxn[0]=-1005;
for(int i=1;i<=n;i++)
{
maxn[i]=max(a[i],a[i]+maxn[i-1]);
}
int maxNum=-1005,End=0;
for(int i=1;i<=n;i++)
{
if(maxn[i]>maxNum)
{
maxNum=maxn[i];End=i;
}
}
printf("Case %d:\n%d",time,maxNum);
int Begin;
int sum=0;
for(int i=End;i>=1;i--)
{
sum+=a[i];
if(sum==maxNum) Begin=i;
}
printf(" %d %d\n",Begin,End);
if(time<T)
printf("\n");
}
return 0;
}</span>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: