1、最大子段和问题

     问题定义:对于给定序列a1,a2,a3……an,寻找它的某个连续子段,使得其和最大。如( -2,11,-4,13,-5,-2 )最大子段是{ 11,-4,13 }其和为20。

(1)枚举法求解

枚举法思路如下:

以a[0]开始: {a[0]}, {a[0],a[1]},{a[0],a[1],a[2]}……{a[0],a[1],……a[n]}共n个

以a[1]开始: {a[1]}, {a[1],a[2]},{a[1],a[2],a[3]}……{a[1],a[2],……a[n]}共n-1个

……

以a[n]开始:{a[n]}共1个

一共(n+1)*n/2个连续子段,使用枚举,那么应该可以得到以下算法:
     具体代码如下:

//3d4-1 最大子段和问题的简单算法
#include "stdafx.h"
#include <iostream>
using namespace std; int MaxSum(int n,int *a,int& besti,int& bestj); int main()
{
int a[] = {-,,-,,-,-}; for(int i=; i<; i++)
{
cout<<a[i]<<" ";
} int besti,bestj; cout<<endl;
cout<<"数组a的最大连续子段和为:a["<<besti<<":"<<bestj<<"]:"<<MaxSum(,a,besti,bestj)<<endl; return ;
} int MaxSum(int n,int *a,int& besti,int& bestj)
{
int sum = ;
for(int i=; i<n; i++)//控制求和起始项
{
for(int j=i; j<n; j++)//控制求和结束项
{
int thissum = ;
for(int k=i; k<=j; k++)//求和
{
thissum += a[k];
} if(thissum>sum)//求最大子段和
{
sum = thissum;
besti = i;
bestj = j;
}
}
}
return sum;
}

从这个算法的三个for循环可以看出,它所需要的计算时间是O(n^3)。事实上,如果注意到,则可将算法中的最后一个for循环省去,避免重复计算,从而使算法得以改进。改进后的代码如下:

//3d4-2 最大子段和问题的避免重复的简单算法
#include "stdafx.h"
#include <iostream>
using namespace std; int MaxSum(int n,int *a,int& besti,int& bestj); int main()
{
int a[] = {-,,-,,-,-}; for(int i=; i<; i++)
{
cout<<a[i]<<" ";
} int besti,bestj; cout<<endl;
cout<<"数组a的最大连续子段和为:a["<<besti<<":"<<bestj<<"]:"<<MaxSum(,a,besti,bestj)<<endl; return ;
} int MaxSum(int n,int *a,int& besti,int& bestj)
{
int sum = ;
for(int i=; i<n; i++)//控制求和起始项
{
int thissum = ;
for(int j=i; j<=n; j++)//控制求和结束项
{
thissum += a[j];//求和
if(thissum>sum)
{
sum = thissum;
besti = i;
bestj = j;
} }
}
return sum;
}

(2)分治法求解

分治法思路如下:

将序列a[1:n]分成长度相等的两段a[1:n/2]和a[n/2+1:n],分别求出这两段的最大字段和,则a[1:n]的最大子段和有三中情形:

[1]、a[1:n]的最大子段和与a[1:n/2]的最大子段和相同;

[2]、a[1:n]的最大子段和与a[n/2+1:n]的最大子段和相同;

[3]、a[1:n]的最大字段和为,且1<=i<=n/2,n/2+1<=j<=n。

可用递归方法求得情形[1],[2]。对于情形[3],可以看出a[n/2]与a[n/2+1]在最优子序列中。因此可以在a[1:n/2]中计算出,并在a[n/2+1:n]中计算出。则s1+s2即为出现情形[3]时的最优值。

具体代码如下:

//3d4-1 最大子段和问题的分治算法
#include "stdafx.h"
#include <iostream>
using namespace std; int MaxSubSum(int *a,int left,int right);
int MaxSum(int n,int *a); int main()
{
int a[] = {-,,-,,-,-}; for(int i=; i<; i++)
{
cout<<a[i]<<" ";
} cout<<endl;
cout<<"数组a的最大连续子段和为:"<<MaxSum(,a)<<endl; return ;
} int MaxSubSum(int *a,int left,int right)
{
int sum = ;
if(left == right)
{
sum = a[left]>?a[left]:;
}
else
{
int center = (left+right)/;
int leftsum = MaxSubSum(a,left,center);
int rightsum = MaxSubSum(a,center+,right); int s1 = ;
int lefts = ;
for(int i=center; i>=left;i--)
{
lefts += a[i];
if(lefts>s1)
{
s1=lefts;
}
} int s2 = ;
int rights = ;
for(int i=center+; i<=right;i++)
{
rights += a[i];
if(rights>s2)
{
s2=rights;
}
}
sum = s1+s2;
if(sum<leftsum)
{
sum = leftsum;
}
if(sum<rightsum)
{
sum = rightsum;
} }
return sum;
} int MaxSum(int n,int *a)
{
return MaxSubSum(a,,n-);
}

算法所需的计算时间T(n)满足一下递归式:

解此递归方程可知:T(n)=O(nlogn)。

(3)动态规划算法求解

算法思路如下:

,则所求的最大子段和为:

由b[j]的定义知,当b[j-1]>0时,b[j]=b[j-1]+a[j],否则b[j]=a[j]。由此可得b[j]的动态规划递推式如下:

b[j]=max{b[j-1]+a[j],a[j]},1<=j<=n。

具体代码如下:

//3d4-1 最大子段和问题的动态规划算法
#include "stdafx.h"
#include <iostream>
using namespace std; int MaxSum(int n,int *a); int main()
{
int a[] = {-,,-,,-,-}; for(int i=; i<; i++)
{
cout<<a[i]<<" ";
} cout<<endl;
cout<<"数组a的最大连续子段和为:"<<MaxSum(,a)<<endl; return ;
} int MaxSum(int n,int *a)
{
int sum=,b=;
for(int i=; i<=n; i++)
{
if(b>)
{
b+=a[i];
}
else
{
b=a[i];
}
if(b>sum)
{
sum = b;
}
}
return sum;
}

上述算法的时间复杂度和空间复杂度均为O(n)。

     2、最大子矩阵和问题
        (1)问题描述:给定一个m行n列的整数矩阵A,试求A的一个子矩阵,使其各元素之和为最大。

(2)问题分析:

用二维数组a[1:m][1:n]表示给定的m行n列的整数矩阵。子数组a[i1:i2][j1:j2]表示左上角和右下角行列坐标分别为(i1,j1)和(i2,j2)的子矩阵,其各元素之和记为:

最大子矩阵问题的最优值为。如果用直接枚举的方法解最大子矩阵和问题,需要O(m^2n^2)时间。注意到,式中,,设,则

容易看出,这正是一维情形的最大子段和问题。因此,借助最大子段和问题的动态规划算法MaxSum,可设计出最大子矩阵和动态规划算法如下:

//3d4-5 最大子矩阵之和问题
#include "stdafx.h"
#include <iostream>
using namespace std; const int M=;
const int N=; int MaxSum(int n,int *a);
int MaxSum2(int m,int n,int a[M][N]); int main()
{
int a[][N] = {{,-,},{-,,},{-,,},{,,-}}; for(int i=; i<M; i++)
{
for(int j=; j<N; j++)
{
cout<<a[i][j]<<" ";
}
cout<<endl;
} cout<<endl;
cout<<"数组a的最大连续子段和为:"<<MaxSum2(M,N,a)<<endl; return ;
} int MaxSum2(int m,int n,int a[M][N])
{
int sum = ;
int *b = new int[n+];
for(int i=; i<m; i++)//枚举行
{
for(int k=; k<n;k++)
{
b[k]=;
} for(int j=i;j<m;j++)//枚举初始行i,结束行j
{
for(int k=; k<n; k++)
{
b[k] += a[j][k];//b[k]为纵向列之和
int max = MaxSum(n,b);
if(max>sum)
{
sum = max;
}
}
}
}
return sum;
} int MaxSum(int n,int *a)
{
int sum=,b=;
for(int i=; i<=n; i++)
{
if(b>)
{
b+=a[i];
}
else
{
b=a[i];
}
if(b>sum)
{
sum = b;
}
}
return sum;
}

以上代码MaxSum2方法的执行过程可用下图表示:


     3、最大m子段和问题

(1)问题描述:给定由n个整数(可能为负数)组成的序列a1,a2,a3……an,以及一个正整数m,要求确定此序列的m个不相交子段的总和达到最大。最大子段和问题是最大m字段和问题当m=1时的特殊情形。

(2)问题分析:设b(i,j)表示数组a的前j项中i个子段和的最大值,且第i个子段含a[j](1<=i<=m,i<=j<=n),则所求的最优值显然为。与最大子段问题相似,计算b(i,j)的递归式为:

其中,表示第i个子段含a[j-1],而项表示第i个子段仅含a[j]。初始时,b(0,j)=0,(1<=j<=n);b(i,0)=0,(1<=i<=m)。

具体代码如下:

//3d4-6 最大m子段问题
#include "stdafx.h"
#include <iostream>
using namespace std; int MaxSum(int m,int n,int *a); int main()
{
int a[] = {,,,-,,,-};//数组脚标从1开始
for(int i=; i<=; i++)
{
cout<<a[i]<<" ";
} cout<<endl;
cout<<"数组a的最大连续子段和为:"<<MaxSum(,,a)<<endl;
} int MaxSum(int m,int n,int *a)
{
if(n<m || m<)
return ;
int **b = new int *[m+]; for(int i=; i<=m; i++)
{
b[i] = new int[n+];
} for(int i=; i<=m; i++)
{
b[i][] = ;
} for(int j=;j<=n; j++)
{
b[][j] = ;
} //枚举子段数目,从1开始,迭代到m,递推出b[i][j]的值
for(int i=; i<=m; i++)
{
//n-m+i限制避免多余运算,当i=m时,j最大为n,可据此递推所有情形
for(int j=i; j<=n-m+i; j++)
{
if(j>i)
{
b[i][j] = b[i][j-] + a[j];//代表a[j]同a[j-1]一起,都在最后一子段中
for(int k=i-; k<j; k++)
{
if(b[i][j]<b[i-][k]+a[j])
b[i][j] = b[i-][k]+a[j];//代表最后一子段仅包含a[j]
}
}
else
{
b[i][j] = b[i-][j-]+a[j];//当i=j时,每一项为一子段
}
}
}
int sum = ;
for(int j=m; j<=n; j++)
{
if(sum<b[m][j])
{
sum = b[m][j];
}
}
return sum;
}

上述算法的时间复杂度为O(mn^2),空间复杂度为O(mn)。其实,上述算法中,计算b[i][j]时,只用到了数组b的第i-1行和第i行的值。因而,算法中只要存储数组b的当前行,不必存储整个数组。另一方面,的值可以在计算i-1行时预先计算并保存起来。计算第i行的值时不必重新计算,节省了计算时间和空间。因此,算法可继续改进如下:

//3d4-7 最大m子段问题
#include "stdafx.h"
#include <iostream>
using namespace std; int MaxSum(int m,int n,int *a); int main()
{
int a[] = {,,,-,,,-};//数组脚标从1开始
for(int i=; i<=; i++)
{
cout<<a[i]<<" ";
} cout<<endl;
cout<<"数组a的最大连续子段和为:"<<MaxSum(,,a)<<endl;
} int MaxSum(int m,int n,int *a)
{
if(n<m || m<)
return ;
int *b = new int[n+];
int *c = new int[n+]; b[] = ;//b数组记录第i行的最大i子段和
c[] = ;//c数组记录第i-1行的最大i-1子段和 for(int i=; i<=m; i++)
{
b[i] = b[i-] + a[i];
c[i-] = b[i];
int max = b[i]; //n-m+i限制避免多余运算,当i=m时,j最大为n,可据此递推所有情形
for(int j=i+; j<=i+n-m;j++)
{
b[j] = b[j-]>c[j-]?b[j-]+a[j]:c[j-]+a[j];
c[j-] = max;//预先保存第j-1行的最大j-1子段和 if(max<b[j])
{
max = b[j];
}
}
c[i+n-m] = max;
} int sum = ;
for(int j=m; j<=n; j++)
{
if(sum<b[j])
{
sum = b[j];
}
}
return sum;
}

上述算法时间复杂度为O(m(n-m)),空间复杂度为O(n)。当m或n-m为常数时,时间复杂度和空间复杂度均为O(n)。

最大子段和问题,最大子矩阵和问题,最大m子段和问题的更多相关文章

  1. 【动态规划】最大连续子序列和,最大子矩阵和,最大m子段和

    1.最大字段和问题 求一个序列最大连续子序列之和. 例如序列[-1,-2,-3,4,5,-6]的最大子段和为4 + 5 = 9. ①枚举法 int MaxSum(int n,int *a){ int ...

  2. 一类适合初学者的DP:最大子段和与最大子矩阵

    最近在水简单DP题,遇到了两道层层递进的DP题,于是记录一下 一.最大子段和 题意: 给出一个长度为n(n<=1e5)的序列,求连续子段的最大值 比如说2 3 -4 5 的最大值是6  而 2 ...

  3. 【动态规划】最大子段和问题,最大子矩阵和问题,最大m子段和问题

    http://blog.csdn.net/liufeng_king/article/details/8632430 1.最大子段和问题      问题定义:对于给定序列a1,a2,a3……an,寻找它 ...

  4. [51NOD1959]循环数组最大子段和(dp,思路)

    题目链接:http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1050 这道题的最大子段和有两种可能,一种是常规的子段和,另一种 ...

  5. hdu1003 dp(最大子段和)

    题意:给出一列数,求其中的最大子段和以及该子段的开头和结尾位置. 因为刚学过DP没几天,所以还会这题,我开了一个 dp[100002][2],其中 dp[i][0] 记录以 i 为结尾的最大子段的和, ...

  6. 17089 最大m子段和

    17089 最大m子段和 时间限制:1000MS  内存限制:65535K提交次数:0 通过次数:0 题型: 编程题   语言: G++;GCC;VC Description "最大m子段和 ...

  7. POJ 2018 Best Cow Fences(二分+最大连续子段和)

    Best Cow Fences Time Limit: 1000MS Memory Limit: 30000K Total Submissions: 14601 Accepted: 4720 Desc ...

  8. 最大m段子段和

    hdu1024 最大m子序列和 给定你一个序列,让你求取m个子段(不想交的子段)并求取这m个子段和的最大值 从二维开始来看dp[i][j]表示取第j个数作为第i个子段的元素所得到的前i个子段和的最大值 ...

  9. 洛谷 P4513 小白逛公园-区间最大子段和-分治+线段树区间合并(单点更新、区间查询)

    P4513 小白逛公园 题目背景 小新经常陪小白去公园玩,也就是所谓的遛狗啦… 题目描述 在小新家附近有一条“公园路”,路的一边从南到北依次排着nn个公园,小白早就看花了眼,自己也不清楚该去哪些公园玩 ...

随机推荐

  1. python——周边

    Pythonic的禅意 import this python是用c语言写的.传说python不止有C语言实现,还有java实现,还有python实现的python,甚至还有js实现的python. p ...

  2. JS中 window的用法

    1.window.location.reload();作用是刷新当前页面

  3. JSP重定向传递参数

    我一个JSP程序,要实现前台提交数据给后台处理后,后台jsp自动跳转到另一个jsp页面,这种方式也叫重定向,重定向的方法有多种,暂时我试过的并且能成功的有两个: 一种是用 response.sendR ...

  4. Web前端的学习介绍(截止今天还有Bootstrap没有学,要腾点时间解决掉)

    Web前端的学习分为以下几个阶段,具体的学习路线图如图所示. 第一阶段——HTML的学习 超文本标记语言(HyperText Mark-up Language 简称HTML)是一个网页的骨架,无论是静 ...

  5. RFID Hacking①:突破门禁潜入FreeBuf大本营

    某天,偶然间拿到了FreeBuf Pnig0s同学的工卡信息,终于有机会去做一些羞羞的事情了 引子 以下故事纯属虚构,如有雷同,纯属巧合. 我应聘了一个大型IT公司的"网络攻击研究部经理&q ...

  6. ios即时通讯客户端开发之-mac上搭建openfire服务器

    一.下载并安装openfire 1.到http://www.igniterealtime.org/downloads/index.jsp下载最新openfire for mac版 比如:Openfir ...

  7. 16、SQL基础整理(触发器.方便备份)

    触发器(方便备份) 本质上还是一个存储过程,只不过不是通过exec来调用执行,而是通过增删改数据库的操作来执行(可以操作视图) 全部禁用触发器 alter table teacher disable ...

  8. VMDK镜像迁移到KVM(二)

    KVM has the ability to use VMware's .vmdk disk files directly, as long as the disk is wholly contain ...

  9. sprintf 用法

    字符串格式化命令,主要功能是把格式化的数据写入某个字符串中 试试下面的代码就知道了 #include<cstdio> #include<cstdlib> using names ...

  10. HttpResponse的Close和End 区别

    转载自:http://blog.sina.com.cn/s/blog_702c390c0100mlhi.html 最近启用了IIS上的压缩功能,但是测试系统上某模块变得不可用了.该模块采用AJAX技术 ...