动态规划_最大m子段和
动态规划_最大m子段和
继最大子段和在空间上的推广,那么再来看看她在个数上的延伸。
最大子段和就是最大m子段和问题在m=1时的特殊情况
类似最大字段和中的b(j)
这里假设b(i,j)表示数组a的前j项中i个子段和的最大值,且第i个子段含a[j]
其中b(i,j-1)+a[j]表示第i个子段含a[j],而,表示第i个子段仅含a[j],所以在表示b(i,j)需要从有i-1个子段中选择一个最大子段和的字段,即找到一个合适的t.
初始时b(0,j)=0,(0<=j<=n);b(i,0)=0,(1<=i<=m)
int MaxSum(int m,int n,int *a)
{
if(n<m||m<1)
return 0;
int **b=new int *[m+1];
int i,j;
for( i=0;i<=m;i++)
b[i]=new int [n+1];
for( j=0;j<=m;j++) //0....m
b[j][0]=0;
for(int k=1;k<=n;k++) //1...m
b[0][k]=0;
for(i=1;i<=m;i++)
for(int j=i;j<=n-m+i;j++)
<span style="color:#FF0000;"><strong>if(j>i)</strong>
{
b[i][j]=b[i][j-1]+a[j];
for(int k=i-1;k<j;k++) //从有i-1个子段中选择一个最大子段和的字段
if(b[i][j]<b[i-1][k]+a[j]) </span> //表示第i个字段仅含a[j],所以需要得到max{b[i-1][k]},这样的b[i][j]才能表示最大i字段和<span style="color:#FF0000;">
b[i][j]=b[i-1][k]+a[j];
}
else //i==j的情况
b[i][j]=b[i-1][j-1]+a[j];</span>
int sum=0;
for( j=m;j<=n;j++)
if(sum<b[m][j])
sum=b[m][j];
printB(b,m,n);
return sum;
}
例如:a[]={3,-4,2,11,-4,13,-5,-2} m=3
根据以上算法段可得出下表,表中蓝色表示初始化,红色xx表示不存在i>j的情况,即b(i,j)不合实际,紫色o表示该字段仅含一个元素,
例如b(i,j),i=1时,j最多只能为6,然后a[7],a[8]各为一个子段,这样才能构成m=3的子段
重点是绿色数字,表示经过了更新的值。例如绿色2,此时i=2,j=3,根据b(i,j-1)+a[j]=-1+2=1,然后从i-1~j-1,也就是1~2,maxb(i-1,t)+a[j]=0+2=2 由于2>1,所以更新b(2,3)=2
再比如说绿5,1(-1+2)->5(3+2) 绿26,25(12+13)->26(13+13) 绿16,12(1+11)->16(5+11).......
最后在b(3,j)中找最大值,即1,16,12,29,24,24,中,找出29为{3,-4,2,11,-4,13,-5,-2}最大3子段和
算法分析:由此可知该算法时间复杂度为O(m*n^2),空间复杂度为O(m*n)
但是经分析可知计算b[i][j]时只用到了数组b的第i-1行和i行的值,因而算法只要储存b的当前行i和i-1行,没必要储存整个二维数组的值。
改进后算法如下:
int MaxSum(int m,int n,int *a)
{
if(n<m||m<1)
return 0;
int *b=new int[n+1];
int *c=new int[n+1];
b[0]=0;
for(int k=0;k<=n;k++) //初始化保存上一轮值的数组
c[k]=0;
for(int i=1;i<=m;i++)
{
b[i]=b[i-1]+a[i]; //第i个字段包含a[i]
c[i-1]=b[i]; //<span style="color:#FF0000;">为什么是c[i-1]=b[i]呢?保存当前最大子段和,因为在下一轮即有2,3个子段时,当前的b[i]会作为b[i-1]来计算新一轮的b[i]</span>
int max=b[i];
for(int j=i+1;j<=i+n-m;j++) //第i个字段仅包含a[i]
{
b[j]=b[j-1]>c[j-1]?b[j-1]+a[j]:c[j-1]+a[j]; //c[j-1]上一轮的最大值,没有的话默认为0
c[j-1]=max; //<span style="color:#FF0000;">非首个成员的话,先是用c[j-1]直接保存上一次最大值,</span>
if(max<b[j]) <span style="color:#FF0000;"> //然后更max,用于下一次,这样的c[]在该轮必定是递增的,</span>
max=b[j];
}
c[i+n-m]=max;//<span style="color:#FF0000;">当然在一轮结束的时候c[]也是要保存当前最大子段和max</span>
cout<<"b[] ";
printA(b,n);
cout<<"c[] ";
printA(c,n);
cout<<endl;
}
int sum=0;
for(int j=m;j<=n;j++)
if(sum<b[j])
sum=b[j];
return sum;
}
运行结果:
红色圆点表示:c[]被更新的地方,
红色斜杠表示:某个子段首个成员更新的情况,如b[1]=3-->c[0]=3,这是直接更新的,但是在计算出b[2]=-1,先是直接赋值c[2],因为此时的最大字段和就是上一次的3嘛,但有所不同的是,这里我会将max和当前最大字段和b[j]比较一下,然后更新,用于下一次的c[j-1]
由此可知该算法的时间复杂度O(m(n-m),空间复杂度为O(n),当m或者n-m为常数时,时间复杂度便可降为O(n)
————————————————
版权声明:本文为CSDN博主「fir_dameng」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u014034497/article/details/45604011