算法第三章上机实践报告
1.1问题描述
给定n个整数(可能为负数)组成a[1],a[2],a[3],…,a[n],求该序列如a[i]+a[i+1]+…+a[j]的子段和的最大值。当所给的整数均为负数时,定义子段和为0。要求算法的时间复杂度为O(n)。
1.2算法描述
#include <bits/stdc++.h>
#define max 10010
using namespace std;
int MaxSum(int n,int *a){
int D[max];
int sum=0;
D[n]=a[n];//以序列结尾为开头的最大子段和必等于该序列结尾的数值
for(int i=n-1;i>=1;i--){
if(D[i+1]<0){
D[i]=a[i]; //D[i]表示以a[i]为开头的最大子段和
}
else{
D[i]=D[i+1]+a[i];// D[i+1]>=0时必须加上a[i]才是D[i]的最优值
}
if(sum<D[i]){
sum=D[i];//记录最优解,即所求的序列最大子段和
}
}
return sum;
}
int main(){
int n;
int a[max];
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
cout<<MaxSum(n,a);
}
1.3问题求解
1.3.1 递归方程
D[i]={D[i+1]+a[i],a[i]}
D[i]表示以a[i]为开头的最大子段和
1.3.2 填表
以本次实验的输入样例为例,给定序列为-2 11 -4 13 -5 -2共6个数,首先将6个数填入a数组中:
a1 |
a2 |
a3 |
a4 |
a5 |
a6 |
-2 |
11 |
-4 |
13 |
-5 |
-2 |
随后填表D,填表顺序是从后往前填:
根据D[i]的含义,D6一定是填-2
D1 |
D2 |
D3 |
D4 |
D5 |
D6 |
|
|
|
|
|
-2 |
随后填D5,由于D6小于0,加上只会让D5更小,所以D5等于a5
D1 |
D2 |
D3 |
D4 |
D5 |
D6 |
|
|
|
|
-5 |
-2 |
再填D4,道理同上,没必要加上D5,所以D4等于a4
D1 |
D2 |
D3 |
D4 |
D5 |
D6 |
|
|
|
13 |
-5 |
-2 |
接下来往前填D3,这时D4是大于0的,所以D3=D4+a3
D1 |
D2 |
D3 |
D4 |
D5 |
D6 |
|
|
9 |
13 |
-5 |
-2 |
依此类推,最后得到的一维表为
D1 |
D2 |
D3 |
D4 |
D5 |
D6 |
18 |
20 |
9 |
13 |
-5 |
-2 |
所以根据表可知,该序列的最大子段和为20
1.3.3算法时间复杂度及空间复杂度
时间复杂度:O(n)
空间复杂度:用到了一维数组,空间复杂度O(n)
1.4 心得体会
一开始不理解D[i]表示的是什么意思,简单地以为D[1]就是最后要求的结果,导致输出的结果出错,经过老师讲解后发现其实D[i]的含义很好理解。通过这次实践,又学到了一个求最大子数组(最大子段和)的更优算法,比起前边分治法求最大子数组,动态规划算法的时间效率又大大提高了。
2.对动态规划算法的理解体会
动态规划算法通常用于求解具有某种最优性质的问题。动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的。若用分治法来解这类问题,则分解得到的子问题数目太多,有些子问题被重复计算了很多次。我们可以用一个表来记录所有已解的子问题的答案。不管该子问题以后是否被用到,只要它被计算过,就将其结果填入表中。在需要时再找出已求得的答案,这样就可以避免大量的重复计算,节省时间。