连续子向量最大和问题——《编程珠玑》读书笔记
问题描述:
问题的输入是具有n个整数的向量x,输出是输入向量的任何子向量中的最大和。例如下面的向量:
31 | -41 | 59 | 26 | -53 | 58 | 97 | -93 | -23 | 84 |
那么改程序的输出就应该是59 到 97之间的总和,即187.但所有数为正数的时候,答案就是整个输入向量,可是我们必须要考虑负数的情况。
问题解答:
1、第一种方法式最简单的,对所有满足0 <= i <= j < n的(i,j)整数对进行迭代。找出总和最大的那个。这种方式的时间复杂度为O(n^3)。具体做法不在赘述。
2、第二种方式是将时间复杂度降低到O(n^2),需要注意到x[i..j]的总和和前面已经计算出得总和x[i..j-1]有密切关系,利用这一关系即可得到算法2:
maxsofar = 0
for i = [0, n)
sum = 0
for j = [i, n)
sum += x[j]
maxsofar = max(maxsofar, sum)
3、分值算法
在本例中,初始问题的大小为n,所以将它划分为两个子问题a、b,然后递归的找出a、b中元素的最大子向量和,ma和mb。但是,我们需要考虑到,最大子向量要么整个在a中,要么整个在b中,也有可能是跨越a和b的边界。那么我们就将跨越边界的最大子向量成为mc。
如何计算mc的值呢,mc在a中的部分是a中包含右边界的最大子向量,而mc在b中的部分则是包含左边界的最大子向量。下面是完整的C++代码:
#include <iostream> using namespace std; #define NUM 10 int max(int a, int b, int c = 0) { if (a < b) { a = b; } if (a < c) { return c; } return a; } int maxsum(int* ari, int begin, int end) { if (begin > end) { return 0; } if (begin == end) { return max(0, ari[begin]); } int m = (begin + end) / 2; int lmax = 0; int rmax = 0; int sum = 0; //find max crossing to left for (int i = m; i >= begin; i--) { sum += ari[i]; lmax = max(lmax, sum); } sum = 0; //find max crossing to right for (int i = m + 1; i < end; i++) { sum +=ari[i]; rmax = max(rmax, sum); } return max(lmax + rmax, maxsum(ari, begin, m), maxsum(ari, m + 1, end)); } int main() { int vi[NUM] = {31, -41, 59, 26, -53, 58, 97, -93, -23, 84}; cout << maxsum(vi, 0, 9) << endl; return 0; }