算法第二章上机实践报告
1.实践题目名称
7-1 最大子列和问题
2.问题描述
给定K个整数组成的序列{ N1, N2, ..., NK },“连续子列”被定义为{ Ni, Ni+1, ..., Nj },其中 1≤i≤j≤K。“最大子列和”则被定义为所有连续子列元素的和中最大者。例如给定序列{ -2, 11, -4, 13, -5, -2 },其连续子列{ 11, -4, 13 }有最大的和20。现要求你编写程序,计算给定整数序列的最大子列和。
3.算法描述
一个序列的最大连续子段和,可能在该序列的左序列、右序列或中序列。用分治法解决。首先,分解子问题:把一个序列分解成三个子序列来解决,分别是左序列,右序列,和中序列。其次,求解“左序列的最大子段和” 与 “求解右序列”又可以看成是“和原问题相同”的子问题,用递归函数来实现求解;中序列则需要特殊考虑,可以从中间的那个元素分别向左、向右求和,最后把两个和加起来,就是中序列的最大子段和。最后,左、中、右序列的最大子段和,三者中最大的就是答案。
具体代码如下:
1 #include<iostream> 2 using namespace std; 3 4 int MaxSum(int a[],int left,int right) 5 { 6 int maxsum = 0; 7 8 //递归的变界条件 9 if(left == right){ 10 if(a[left] > 0){ 11 return a[left]; 12 } 13 else{ 14 return 0; //如果序列中所有整数皆为负数,则输出0 15 } 16 } 17 else{ 18 int mid = (left + right) / 2; 19 int leftsum = MaxSum(a, left, mid); //求左序列最大子段和 20 int rightsum = MaxSum(a, mid+1, right); //求右序列最大子段和 21 22 //求中间序列最大子段和 23 int left_border_sum = 0; 24 int s1 = 0; //临时累加器1 25 for(int i = mid; i >= left; i--) 26 { 27 s1 += a[i]; 28 if(s1 > left_border_sum) 29 { 30 left_border_sum = s1; 31 } 32 } 33 int right_border_sum = 0; 34 int s2 = 0; //临时累加器2 35 for(int i = mid+1; i <= right; i++) 36 { 37 s2 += a[i]; 38 if(s2 > right_border_sum) 39 { 40 right_border_sum = s2; 41 } 42 } 43 44 int midsum = left_border_sum + right_border_sum; 45 46 maxsum = leftsum > rightsum ? leftsum : rightsum; 47 maxsum = midsum > maxsum ? midsum : maxsum; 48 49 return maxsum; 50 } 51 52 } 53 54 int main() 55 { 56 int K,a[200005]; 57 cin>>K; 58 for(int i = 0; i < K; i++) 59 { 60 cin>>a[i]; 61 } 62 63 cout<<MaxSum(a, 0, K-1); 64 return 0; 65 }
4.算法时间及空间复杂度分析(要有分析过程)
一、时间复杂度分析:
①分解子问题:将原序列一分为二,时间复杂度为O(1)
②求解子问题:子问题的规模,是原来问题规模的一半;原问题分解为两个相同的子问题。所以时间复杂度为2T(n/2)
③合并子问题的解:每一次递归调用,就合并一次子问题的解。一共需要递归调用n/2次,所以时间复杂度为O(n)
解此递归方程可得,T(n) = O(nlogn)
二、空间复杂度分析:
空间复杂度为O(n),用于存储输入数据
5.心得体会(对本次实践收获及疑惑进行总结)
本次实践过程中对“子问题划分的边界值”产生了疑惑,这涉及到序列的个数是奇数还是偶数。一开始我写的是:
int leftsum = MaxSum(a, left, mid-1); //求左序列最大子段和 int rightsum = MaxSum(a, mid, right); //求右序列最大子段和
这样子会产生错误。举个例子:若序列的只有两个,则left=0,right=1,mid=0。此时划分出的左序列就是从0到-1,而右序列是从0-1。不仅是左序列的划分变界可能会出错,而且右序列也没有缩小问题规模的大小。
这涉及到mid的向上取整还是向下取整的问题。正确写法为:
int leftsum = MaxSum(a, left, mid); //求左序列最大子段和
int rightsum = MaxSum(a, mid+1, right); //求右序列最大子段和
通过这道题,我对分治思想有了更深刻的理解,对分治法中的“划分子问题”还需要更好的把握。