思路来自于微软的《编程之美》。注意:子序列(subsequence)和连续子数组还是不一样的,子序列可以是不连续的。
/*****************************
列出了四种算法,复杂度从O(N^3)到O(N)
Author: MicroGrape
Date:2009-5-20
*****************************/
#include <iostream>
using namespace std;
/*
O(N^3) algorithm
*/
int MaxSubSumA(int a[], int n)
{
int maxsum = 0;
for(int i = 0; i < n ; i++)
{
for(int j = i ; j < n ; j++)
{
int sum = 0;
for(int k = i ; k <= j ; k++)
sum += a[k];
if(sum > maxsum)
maxsum = sum;
}
}
return maxsum;
}
/*
O(N^2) algorithm
充分利用了中间数据。
例如上一轮从a[4]加到a[8],下一轮从a[4]加到a[9]时。
实际上可以利用上面的结果,减少重复计算。
即:以a[i]起头的最大和子序列只可能有一个。
*/
int MaxSubSumB(int a[], int n)
{
int maxsum = 0;
for(int i = 0; i < n ; i++)
{
int sum = 0;
for(int j = i ; j < n ; j++)
{
sum += a[j];
if(sum > maxsum)
maxsum = sum;
}
}
return maxsum;
}
/*
O(N*logN) recursive algorithm
采用了分治和递归的思想。将一个数组分成两个数组处理。
*/
int MaxSubSumC(int a[], int left, int right)
{
//如果某边剩一个元素,如果大于0则累加,小于0则放弃
if(left == right)
if(a[left] > 0)
return a[left];
else
return 0;
int middle = left + (right -left)/2;
//左半边中子序列的最大和
int leftsum = MaxSubSumC(a, left, middle);
//右半边中子序列的最大和
int rightsum = MaxSubSumC(a, middle+1, right);
//跨两边子序列的最大和,所以从middle往两边起算
int leftrightsum = 0;
//首先middle往左。一个标记最大值,一个标记临时值。
int middleleftsum = 0, maxmiddleleftsum = 0;
for(int i = middle ; i >= left ; i--)
{
middleleftsum += a[i];
if(middleleftsum > maxmiddleleftsum)
maxmiddleleftsum = middleleftsum;
}
//然后middle往右
int middlerightsum = 0, maxmiddlerightsum = 0;
for(int j = middle+1 ; j <= right ; j++)
{
middlerightsum += a[j];
if(middlerightsum > maxmiddlerightsum)
maxmiddlerightsum = middlerightsum;
}
leftrightsum = maxmiddleleftsum + maxmiddlerightsum;
//取三者中的最大值
return max(leftsum, max(rightsum, leftrightsum) );
}
//为了保持统一接口,给C写了一个驱动程序
int MaxSubSumC(int a[], int n)
{
return MaxSubSumC(a, 0, n-1);
}
/*
O(N) algorithm
一个以a[0]开头的到a[i]子序列只有三种情况:
1.为负。则抛弃这个结果,从a[i+1]开始重新计算(减少了计算量)//else if
2.大于已知的最大和,记录下来。//if
3.最大和还未出现,继续累加下去。//未被以上两者匹配。
*/
int MaxSubSumD(int a[], int n)
{
int maxsum = 0, sum = 0;
for(int i = 0; i < n ; i++)
{
sum += a[i];
if(sum > maxsum)
maxsum = sum;
else if (sum < 0)
sum = 0 ;
}
return maxsum;
}
列出了四种算法,复杂度从O(N^3)到O(N)
Author: MicroGrape
Date:2009-5-20
*****************************/
#include <iostream>
using namespace std;
/*
O(N^3) algorithm
*/
int MaxSubSumA(int a[], int n)
{
int maxsum = 0;
for(int i = 0; i < n ; i++)
{
for(int j = i ; j < n ; j++)
{
int sum = 0;
for(int k = i ; k <= j ; k++)
sum += a[k];
if(sum > maxsum)
maxsum = sum;
}
}
return maxsum;
}
/*
O(N^2) algorithm
充分利用了中间数据。
例如上一轮从a[4]加到a[8],下一轮从a[4]加到a[9]时。
实际上可以利用上面的结果,减少重复计算。
即:以a[i]起头的最大和子序列只可能有一个。
*/
int MaxSubSumB(int a[], int n)
{
int maxsum = 0;
for(int i = 0; i < n ; i++)
{
int sum = 0;
for(int j = i ; j < n ; j++)
{
sum += a[j];
if(sum > maxsum)
maxsum = sum;
}
}
return maxsum;
}
/*
O(N*logN) recursive algorithm
采用了分治和递归的思想。将一个数组分成两个数组处理。
*/
int MaxSubSumC(int a[], int left, int right)
{
//如果某边剩一个元素,如果大于0则累加,小于0则放弃
if(left == right)
if(a[left] > 0)
return a[left];
else
return 0;
int middle = left + (right -left)/2;
//左半边中子序列的最大和
int leftsum = MaxSubSumC(a, left, middle);
//右半边中子序列的最大和
int rightsum = MaxSubSumC(a, middle+1, right);
//跨两边子序列的最大和,所以从middle往两边起算
int leftrightsum = 0;
//首先middle往左。一个标记最大值,一个标记临时值。
int middleleftsum = 0, maxmiddleleftsum = 0;
for(int i = middle ; i >= left ; i--)
{
middleleftsum += a[i];
if(middleleftsum > maxmiddleleftsum)
maxmiddleleftsum = middleleftsum;
}
//然后middle往右
int middlerightsum = 0, maxmiddlerightsum = 0;
for(int j = middle+1 ; j <= right ; j++)
{
middlerightsum += a[j];
if(middlerightsum > maxmiddlerightsum)
maxmiddlerightsum = middlerightsum;
}
leftrightsum = maxmiddleleftsum + maxmiddlerightsum;
//取三者中的最大值
return max(leftsum, max(rightsum, leftrightsum) );
}
//为了保持统一接口,给C写了一个驱动程序
int MaxSubSumC(int a[], int n)
{
return MaxSubSumC(a, 0, n-1);
}
/*
O(N) algorithm
一个以a[0]开头的到a[i]子序列只有三种情况:
1.为负。则抛弃这个结果,从a[i+1]开始重新计算(减少了计算量)//else if
2.大于已知的最大和,记录下来。//if
3.最大和还未出现,继续累加下去。//未被以上两者匹配。
*/
int MaxSubSumD(int a[], int n)
{
int maxsum = 0, sum = 0;
for(int i = 0; i < n ; i++)
{
sum += a[i];
if(sum > maxsum)
maxsum = sum;
else if (sum < 0)
sum = 0 ;
}
return maxsum;
}
附注一:最后一种算法的思想很巧妙,还有几点很值得说的。
比如
(1)代码是如何使用一个sum值和maxsum值,实际上就给每一个数组的元素保存了一份信息。
(2)通过上面一点,我们知道,最大和的结尾肯定是可以被记录下来的,因为每一个最大和都被记录了。但是怎么保证开头也是符合最大和要求的呢?其实最大和序列的起始要求只有一个:不能为负。所以里面的else if就保证了这一点。