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

来源:互联网 发布:华北电力网络中心 楼 编辑:程序博客网 时间:2024/06/07 23:14

最大连续子序列,即从一个序列中选取一段连续的子序列,使这个子序列的和最大,例如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>



0 0