zoukankan      html  css  js  c++  java
  • 最大连续和问题

    给出一个长度为n的序列A1,A2,…,An,求最大连续和。换句话说,要求找到1<=i<=j<=n,使得Ai+Ai+1+…Aj尽量大。

    暴力枚举实现

    #include <iostream>
    using namespace std;
    int A[50]={5,9,7,3,4,1,5,8,6,7,3,6,4,0,8,6,7};
    int main()
    {
     	int n=50;
    	int tot=0;
    	int best=A[0];
    	//初始化最大值,为了防止连续和小于0,所以必须初始化为A[0]。
    	for(int i=0;i<n;i++)
    	{
    		for(int j=i;j<n;j++)
    		{//检查连续子序列A[i],…,A[j]
    			int sum=0;
    			for(int k=i;k<=j;k++)
    			{//累加元素和
    				sum+=A[k];
    				tot++;
    			}
    			if(sum>best)
    			{
    				best=sum;
    			}
    		}
    	}
    	cout<<"best="<<best<<' '<<"tot="<<tot<<endl;
    	return 0;
    }
    

    tot与机器的运行速度无关。
    不同机器的速度不一样,运行时间也会有所差异,但tot的值一定相同。
    换句话说,它去掉了机器相关的因素,只衡量算法的“工作量”大小,
    具体来说,是“加法”操作的次数。

    算法分析:

    1.第一层循环遍历数组A的i位从0到n,是最大连续和的左起点;
    2.第二层循环遍历数组A的j位从i到n,是最大连续和的右终点;
    3.第三层循环遍历数组A的k位从i到j,求出当前范围内的连续和;
    4.公式:设输入规模为n时的加法操作次数
    T(n)= i=1nj=inji+1\displaystyle\sum_{i=1}^{n} \displaystyle\sum_{j=i}^{n} j-i+1=i=1n\displaystyle\sum_{i=1}^{n} (ni+1)(ni+2)2\frac{(n-i+1)(n-i+2)}{2}=n(n+1)(n+2)6\frac{n(n+1)(n+2)}{6}

    连续子序列之和等于两个前缀和之差实现

    #include <iostream>
    using namespace std;
    int A[50]={5,9,7,3,4,1,5,8,6,7,3,6,4,0,8,6,7};
    int S[50];
    int main()
    {
    	int n=50,best=A[0];
    	S[0]=0;
    	for(int i=0;i<n;i++)
    	{
    		S[i]=S[i-1]+A[i];
    		//递推前缀和S
    	}
    	for(int i=0;i<n;i++)
    	{
    		for(int j=i;j<n;j++)
    		{
    			best=max(best,S[j]-S[i-1]);
    			//更新最大值
    		}
    	}
    	cout<<"best="<<best<<endl;
    	return 0;
    }
    

    算法分析:

    S[i]表示第1个数到第i个数的和,S[j]-S[i-1]表示第i个数到第j个数列的和。
    T(n)=i=1nni+1\displaystyle\sum_{i=1}^{n} n-i+1=n(n+1)2\frac {n(n+1)}{2}

    分治算法实现

    #include <iostream>
    using namespace std;
    int A[50]={5,9,7,3,4,1,5,8,6,7,3,6,4,0,8,6,7};
    int maxsum(int *A,int x,int y)
    {//返回数组在左闭右开区间[x,y)中的最大连续和
    	if(y-x==1) return A[x];    //只有一个元素,直接返回。
    	int m=x+(y-x)/2;
    	//分治第一步:划分成[x,m)和[m,y)
    	int maxs=max(maxsum(A,x,m),maxsum(A,m,y));
    	//分治第二步:递归求解
    	int v,L,R;
    	v=0,L=A[m-1];
    	//分治第三步:合并(1)——从分界点开始往左的最大连续和L
    	for(int i=m-1;i>=x;i--)
    	{
    		L=max(L,v+=A[i]);
    	}
    	v=0,R=A[m];
    	//分支第三步:合并(2)——从分界点开始往右的最大连续和R
    	for(int i=m;i<y;i++)
    	{
    		R=max(R,v+=A[i]);
    	}
    	return max(maxs,L+R);    //把子问题的解与L和R比较
    }
    int main()
    {
        cout<<maxsum(A,0,51)<<endl;
    	return 0;
    }
    

    分治算法实现:
    1.划分问题:把序列分成元素个数尽量相等的两半;
    2.递归求解:分别求出完全位于左半或者完全位于右半的最佳序列;
    3.合并问题:求出起点位于左半,终点位于右半的最大连续和序列,并和子问题的最优解比较。

    思路分析

    首先,我们可以把整个数列平均分成左右两部分,答案则会在以下三种情况中:
    1、所求数列完全包含在左半部分的数列中。
    2、所求数列完全包含在右半部分的数列中。
    3、所求数列刚好横跨分割点,即左右数列各占一部分。
    前两种情况和大问题一样,只是规模小了些,如果三个子问题都能解决,那么答案就是三个结果的最大值。
    在这里插入图片描述
    计算出:以分割点为起点向左的最大连续数列和、以分割点为起点向右的最大连续数列和,这两个结果的和就是第三种情况的答案。因为已知起点,所以这两个结果都能在O(N)的时间复杂度能算出来。

    算法分析:

    用递归的思路进行分析,设序列长度为n时T(n)=2T(n/2)+n,T(1)=1;
    其中2T(n/2)是两次长度为n/2的递归调用,而最后的n是合并的时间(整个序列恰好扫描一遍)。
    注意分界点应当是x和y的平均数m=x+(y-x)/2,这是因为运算符“/”的“取整”朝零方向(towards zero)
    的取整,而不是向下取整用x+(y-x)/2来确保分界点总是靠近区间起点。

    动态规划实现

    #include <iostream>
    using namespace std;
    int A[50]={0,5,9,7,3,4,1,5,8,6,7,3,6,4,0,8,6,7};
    int main()
    {
        int ans=A[1];
        for(int i=1;i<50;i++)
    	{
            if(A[i-1]>0)A[i]+=A[i-1];
            else A[i]+=0;
            if(A[i]>ans) ans=A[i];
        }
        cout<<ans<<endl;
        return 0;
    }
    

    算法分析

    用dp[n]表示以第n个数结尾的最大连续子数列的和,于是存在以下递推公式:
    dp[n] = max(0, dp[n-1]) + num[n]
    则整个问题的答案是max(dp[m]) | m∈[1, N]。

    大道至简——完美解决实现

    #include <iostream>
    using namespace std;
    int main()
    {
        int N,n,s,ans,m=0;
        cin>>N>>n; 	//读取数组长度和数列中的第一个数
        ans=s=n;    //把ans初始化为数列中的的第一个数
        for(int i=1;i<N;i++)
    	{
            if(s<m) m=s;
            cin>>n; s+=n;
            if(s-m>ans)
            ans=s-m;
        }
        cout<<ans<<endl;
        return 0;
    }
    

    已知一个sum数组,sum[i]表示第1个数到第i个数的和,于是sum[j] - sum[i-1]表示第i个数到第j个数的和。

    以第n个数为结尾的最大子数列,假设这个子数列的起点是m,于是结果为sum[n] - sum[m-1]。

    并且,sum[m]必然是sum[1],sum[2]…sum[n-1]中的最小值。
    这样,如果在维护计算sum数组的时候,同时维护之前的最小值, 那么答案也就出来了。

    在计算前缀和的过程中维护之前得到的最小值。
    它的时间复杂度是O(N),空间复杂度是O(1),这达到了理论下限!

    最大连续和问题,解决!

  • 相关阅读:
    TSQL存储过程:获取父级类别图片
    ASP.NET小收集<7>:JS创建FORM并提交
    JS收集<4>:限制输入搜索串
    js编码风格
    学习日志0504
    记于20120508日晚
    NHibernate中的Session yangan
    SQL Server2005排名函数 yangan
    让IE8兼容网页,简单开启兼容模式 yangan
    Log4Net跟我一起一步步 yangan
  • 原文地址:https://www.cnblogs.com/AlexKing007/p/12338524.html
Copyright © 2011-2022 走看看