最近在看数据结构与算法分析这本书。书中第一章描述了这样一个问题:输入一组整数,求出这组数字子序列和中最大值。
例如:1、序列:-2 11 -4 13 -5 -2,则最大子序列和为20。
2、序列:-6 2 4 -7 5 3 2 -1 6 -9 10 -2,则最大子序列和为16。
算法一:使用穷举法计算每个子序列和,从中获得最大值。这个想法是最简单,它的时间复杂度为O(N^3)。在处理小数据量的时候不会有很大的影响,但是在处理大数量的时候,将要花费大量的时间。
public static int MaxSubsequenceSum_1(int[] a)
{
int N = a.Length;
int MaxNum;
MaxNum = 0;
for (int i = 0; i < N; i++)
{
for (int j = i; j < N; j++)
{
int ThisNum;
ThisNum = 0;
for (int k = i; k < j; k++)
{
ThisNum += a[k];
}
if (ThisNum > MaxNum)
{
MaxNum = ThisNum;
}
}
}
return MaxNum;
}
算法二:通过对算法一的研究可以发现,算法一可以进行优化。如算法二所示。跟算法一进行对比,算法二的时间复杂度为O(N^2)。该算法是计算N轮,每轮以不同的起点数进行计算,获得以该起点数最大的子序列和
public static int MaxSubsequenceSum_2(int[] a)
{
int N = a.Length;
int MaxNum = 0;
for (int i = 0; i < N; i++)
{
int ThisNum = 0;
for (int j = i; j < N; j++)
{
ThisNum += a[j];
if (ThisNum > MaxNum)
{
MaxNum = ThisNum;
}
}
}
return MaxNum;
}
算法三:通过对最大子序列和问题的分析,我们可以得到结论:一个负数不可能成为一最大子序列的起点,一个子序列和为负数他也不可能成为最大子序列的前缀。因此,我们可以得到算法三,时间复杂度为O(N)。
public static int MaxSubsequenceSum_3(int[] a)
{
int N = a.Length;
int ThisNum, MaxNum;
ThisNum = 0;
MaxNum = 0;
for (int i = 0; i < N; i++)
{
ThisNum += a[i];
if (ThisNum > MaxNum)
MaxNum = ThisNum;
else if (ThisNum < 0)
ThisNum = 0;
}
return MaxNum;
}
算法四:计算机编程算法中有一个重要的策略是“分治策略”。该策略的思想是:将一个大问题分解成若干个与原问题形式相同的小问题。通过解决这些小问题,从而将原来的大问题得以解决。最大子序列和这个问题也可以采用该策略进行处理。
针对最大子序列和这个问题,我们发现:最大子序列可能在三个地方出现,或者在左半部,或者在右半部,或者跨越输入数据的中部而占据左右两部分。前两种情况递归求解,第三种情况的最大和可以通过求出前半部分最大和(包含前半部分最后一个元素)以及后半部分最大和(包含后半部分的第一个元素)相加而得到。该算法的时间复杂度为O(NlogN)。
public static int MaxSubsequenceSum_4(int[] a, int left, int right)
{
if (left == right)
{
if (a[left] > 0)
{
return a[left];
}
else
return 0;
}
else
{
int MaxLeftNum, MaxRightNum;
int Center = (left + right) / 2;
MaxLeftNum = MaxSubsequenceSum_3(a,left,Center);
MaxRightNum =MaxSubsequenceSum_3(a,Center+1,right);
int LeftBorderNum, RightBorderNum;
LeftBorderNum = 0;
RightBorderNum = 0;
int MaxLeftBorderNum, MaxRightBorderNum;
MaxRightBorderNum = 0;
MaxLeftBorderNum = 0;
for (int i = Center; i >= left; i--)
{
LeftBorderNum += a[i];
if (LeftBorderNum > MaxLeftBorderNum)
{
MaxLeftBorderNum = LeftBorderNum;
}
}
for (int j = Center+1; j <= right; j++)
{
RightBorderNum += a[j];
if (RightBorderNum > MaxRightBorderNum)
{
MaxRightBorderNum=RightBorderNum;
}
}
return Math.Max(Math.Max(MaxRightNum, MaxLeftNum), MaxRightBorderNum + MaxLeftBorderNum);
}
}