给定一个整数序列,a0, a1, a2, …… , an(项可以为负数),求其中最大的子序列和。
即输入一组整数,求出这组数字子序列和中的最大值,只要求出最大子序列的和,不必求出最大值对应的序列。
最大子序列和:整数序列A1, A2,... An (可能有负数),求A1~An的一个子序列Ai~Aj,使得Ai到Aj的和最大。
例如:
序列:-2, 11, -4, 13, -5, 2, -5, -3, 12, -9,则最大子序列和为21。
序列:0, -3, 6, 8, -20, 21, 8, -9, 10, -1, 3, 6, 5,则最大子序列和为43。
方法一:
用三层循环,想要找到这个子序列,肯定是需要循环遍历,有点类似于冒泡排序的感觉。
所以第一层循环确定起点,第二层循环确定终点,第三层循环在起点和终点之间遍历。
时间复杂度:O(N^3)
即算法的运行时间是以输入长度的立方增长,当输入长度过长,程序的运行效率将变得越来越大。
void MaxSumOne(int a[], int N)
{
int ThisSum, MaxSum, i, j, k;
MaxSum = a[0];
for (i = 0; i < N; i++)
{
for (j = i; j < N; j++)
{
ThisSum = 0;
for (k = i; k <= j; k++)
{
ThisSum += a[k];
}
if (ThisSum > MaxSum)
{
MaxSum = ThisSum;
}
}
}
printf("第一种方式求最大子序和为:%d \n",MaxSum);
}
方法二:
相当于改进第一种方法,只使用两层循环,就是不用每次都重新加一遍,每次加完比较一遍。
时间复杂度:O(N^2)
算法的运行时间是以输入长度的平方增长。
void MaxSumTwo(int a[], int N)
{
int ThisSum, MaxSum, i, j;
MaxSum = a[0];
for (i = 0; i < N; i++)
{
ThisSum = 0;
for (j = i; j < N; j++)
{
ThisSum += a[j];
if (ThisSum > MaxSum)
MaxSum = ThisSum;
}
}
printf("第二种方式求最大子序和为:%d \n", MaxSum);
}
方法三:
采用递归的思想,把序列分成左右两个序列,分别求两个序列的最大序列和,这个序列大最大序列的位置只有三种情况:
1、最大序列和完全在左边部分;
2、最大序列和完全在右边部分;
3、最大序列和跨越左右两部分。
所以为了求出结果,可以分别求出左部分的最大序列和、右部分的最大序列和和跨越左右两部分的最大序列和,然后进行比较,最大的即为所求。
求左半部分的最大子序列和,可把左半部分作为新的输入序列通过该算法递归求出。右半部分的最大子序列和也同理。
跨越左右两部分的最大子序列和即为两者相加。
时间复杂度:O(N logN)
int MaxSumThree(int a[], int left, int right)
{
//数组只有一个元素
if (left == right)
{
//空序也是一种情况,返回0
if (a[left] > 0)
return a[left];
else
return 0;
}
//左右递归求出部分序列最大和
int center = (left + right) / 2;
int maxLeftSum = MaxSumThree(a, left, center);
int maxRightSum = MaxSumThree(a, center + 1, right);
//左
int maxLeftBorderSum = 0, leftBorderSum = 0;
for (int i = center; i >= left; i--)
{
leftBorderSum += a[i];
if (leftBorderSum > maxLeftBorderSum)
{
maxLeftBorderSum = leftBorderSum;
}
}
//右
int maxRightBorderSum = 0, rightBorderSum = 0;
for (int i = center + 1; i <= right; i++)
{
rightBorderSum += a[i];
if (rightBorderSum > maxRightBorderSum)
{
maxRightBorderSum = rightBorderSum;
}
}
//跨越两边的
int maxLeftRightSum = maxLeftBorderSum + maxRightBorderSum;
//求三个方面的最大序列
int maxSubSum = 0;
maxSubSum = maxLeftSum > maxRightSum ? maxLeftSum : maxRightSum;
maxSubSum = maxSubSum > maxLeftRightSum ? maxSubSum : maxLeftRightSum;
return maxSubSum;
}
方法四:
设序列为a,长度为n,从a(0)开始求和并记录最大值,即开始计算a(0) , a(0)+a(1) , a(0)+a(1)+a(2)......,直到出现小于0的停止,若加到a(i)以后求和小于0,便从a(i+1)之后重新求和。
因为a(i-1)一定是大于0的,加上a(i)求和小于0,即a(i)是小于0的,之后求和的序列不必加上这个小于0的和值。
时间复杂度:O(N)
void MaxSumFour(int a[], int N)
{
int ThisSum, MaxSum, i;
MaxSum = a[0];
ThisSum = 0;
for (i = 0; i < N; i++)
{
ThisSum += a[i];
if (ThisSum < 0)
ThisSum = 0;
else if (ThisSum > MaxSum)
MaxSum = ThisSum;
}
printf("第四种方式求最大子序和为:%d \n", MaxSum);
}
最后调试使用的主程序为:
#include<stdio.h>
#include<windows.h>
//时间复杂度为N**3
void MaxSumOne(int a[], int N)
{
int ThisSum, MaxSum, i, j, k;
MaxSum = a[0];
for (i = 0; i < N; i++)
{
for (j = i; j < N; j++)
{
ThisSum = 0;
for (k = i; k <= j; k++)
{
ThisSum += a[k];
}
if (ThisSum > MaxSum)
{
MaxSum = ThisSum;
}
}
}
printf("第一种方式求最大子序和为:%d \n",MaxSum);
}
//时间复杂度为N**2
void MaxSumTwo(int a[], int N)
{
int ThisSum, MaxSum, i, j;
MaxSum = a[0];
for (i = 0; i < N; i++)
{
ThisSum = 0;
for (j = i; j < N; j++)
{
ThisSum += a[j];
if (ThisSum > MaxSum)
MaxSum = ThisSum;
}
}
printf("第二种方式求最大子序和为:%d \n", MaxSum);
}
//递归求解(N * logN)
int MaxSumThree(int a[], int left, int right)
{
//数组只有一个元素
if (left == right)
{
//空序也是一种情况,返回0
if (a[left] > 0)
return a[left];
else
return 0;
}
//左右递归求出部分序列最大和
int center = (left + right) / 2;
int maxLeftSum = MaxSumThree(a, left, center);
int maxRightSum = MaxSumThree(a, center + 1, right);
//左
int maxLeftBorderSum = 0, leftBorderSum = 0;
for (int i = center; i >= left; i--)
{
leftBorderSum += a[i];
if (leftBorderSum > maxLeftBorderSum)
{
maxLeftBorderSum = leftBorderSum;
}
}
//右
int maxRightBorderSum = 0, rightBorderSum = 0;
for (int i = center + 1; i <= right; i++)
{
rightBorderSum += a[i];
if (rightBorderSum > maxRightBorderSum)
{
maxRightBorderSum = rightBorderSum;
}
}
//跨越两边的
int maxLeftRightSum = maxLeftBorderSum + maxRightBorderSum;
//求三个方面的最大序列
int maxSubSum = 0;
maxSubSum = maxLeftSum > maxRightSum ? maxLeftSum : maxRightSum;
maxSubSum = maxSubSum > maxLeftRightSum ? maxSubSum : maxLeftRightSum;
return maxSubSum;
}
//时间复杂度为N
void MaxSumFour(int a[], int N)
{
int ThisSum, MaxSum, i;
MaxSum = a[0];
ThisSum = 0;
for (i = 0; i < N; i++)
{
ThisSum += a[i];
if (ThisSum < 0)
ThisSum = 0;
else if (ThisSum > MaxSum)
MaxSum = ThisSum;
}
printf("第四种方式求最大子序和为:%d \n", MaxSum);
}
int main()
{
int arr1[] = { 0, -3, 6, 8, -20, 21, 8, -9, 10, -1, 3, 6, 5};
int N1 = sizeof(arr1) / sizeof(arr1[0]);
MaxSumOne(arr1,N1);
MaxSumTwo(arr1,N1);
printf("第三种方式求最大子序和为:%d \n", MaxSumThree(arr1, 0, N1));
MaxSumFour(arr1, N1);
system("pause");
return 0;
}
其实第一个和第二个方法是最容易想出来的,但是用来解决例子是没有问题的,但是放到实际应用中就显得捉襟见肘了。后面两种方法比较难理解,但是是一种思想。