今天数据结构课讲到这个问题的算法。之前没有考虑过这个问题,留一个代码备份。
分治策略 divide and conquer
在例子中,最大子序列可能在三个部分出现,整个在数据左部分,或整个在右部分,或跨越输入数据中部同时占据两部分。前两种情况可以递归求解,第三种情况的最大和可以通过求出前半部分最大和(含前半部分最后一个元素)和后半部分最大和(含后半部分第一个元素)加和得到。
eg:
4 -3 5 -2 | -1 2 6 -2
前半部分 | 后半部分
前半部分最大子序列之和为6,A1-A3。后半部分最大为8,A6-A7。
前半部分包括最后一个元素的前提下,最大和为4,A1-A4。后半部分包括第一个元素的情况下最大和为7,A5-A7,二者相加即中间最大项11。
比较前部分最大和,后部分最大和,中间最大和。得到最大子序列项。
递归过程调用的一般形式是传递输入的数组以及左右边界,界定了数组要被处理的部分,单行驱动程序通过传递数组以及边界0和N-1而启动该过程。
1-4行处理基准情况,如果Left == Right,那么只有一个元素,并且当该元素为非负时他就是最大和子序列。left > right 的情况不会发生(除非N为负数)。6.7行两次递归调用。8-12和13-17行计算达到中间分界处的两个最大和的和数。这两个最大和的和为扩展到左右两边的最大和。
代码:
static int MaxSubSum( const int A[], int Left, int Right)
{
int MaxLeftSum, MaxRightSum;
int MaxLeftBorderSum, MaxRightBorderSum;
int LeftBorderSum, RightBorderSum;
int Center, i;
if( Left == Right )
if( A[Left] > 0)
return A[Left];
else
return 0;
// 左部分最大值和右部分最大值用递归求解
Center = (Left + Right) / 2;
MaxLeftSum = MaxSubSum(A, Left, Center);
MaxRightSum = MaxSubSum(A, Center+1, Right);
// 包含左侧最后一个元素的序列和,从其中找最大的。
MaxLeftBorderSum = 0;
LeftBorderSum = 0;
for (i=Center; i>=Left; i--)
{
LeftBorderSum += A[i];
if (LeftBorderSum > MaxLeftBorderSum)
{
MaxLeftBorderSum = LeftBorderSum;
}
}
// 包含右侧最后一个元素的序列和,从其中找最大的。
MaxRightBorderSum = 0;
RightBorderSum = 0;
for (i=Center+1; i<=Right; i++)
{
RightBorderSum += A[i];
if (RightBorderSum > MaxRightBorderSum)
{
MaxRightBorderSum = RightBorderSum;
}
}
return Max3(MaxLeftSum, MaxRightSum, MaxLeftBorderSum + MaxRightBorderSum);
}
int MaxSubsequenceSum(const int A[], int N)
{
return MaxSubSum(A, 0, N-1);
}
int Max3(int a, int b, int c)
{
if (a < b)
{
a = b;
}
if (a > c)
return a;
else
return c;
}
联机算法
从别的地方看了一下解释。本来还懵逼着。不过大概意思好像是推进。
如果数小于0,就可以直接清零重置。同时把最大值保存下来。不太明白要怎么解释比较好,但这种扫描一次的结果的算法简直666。书上写仅需要常量空间并以线性时间运行的联机算法几乎是完美的算法。
int MaxSubsequenceSum(const int A[], int N)
{
int ThisSum, MaxSum, j;
ThisSum = MaxSum = 0;
for(j=0; j<N; j++)
{
ThisSum += A[j];
if (ThisSum > MaxSum)
{
MaxSum = ThisSum;
}
else if (ThisSum < 0)
{
ThisSum = 0;
}
return MaxSum;
}
}