算法复杂度:最大子列和问题四种解法(C语言描述)
涉及英文
subcolumn:子列
问题描述
百度百科:
在计算机科学中,最大子数列问题的目标是在数列的一维方向找到一个连续的子数列,使该子数列的和最大。
例如,对一个数列 −2, 1, −3, 4, −1, 2, 1, −5, 4,其连续子数列中和最大的是 4, −1, 2, 1, 其和为6。
算法
算法一:穷举法
-
算法描述
遍历整个数列的所有数值,在每一个扫描点计算以该点数值为开始点的子数列的和。计算所有子列和,并找出最大值。
-
代码示例
int MaxSubseqSum1(int *List,int length) { int i = 0,j = 0,k=0; int sum = 0, max_sum = 0; //算出所以子列的和并选出最大一个子列和 //i表示子列的左端 for (i = 0; i < length; i++) { //j表示子列的右端,j>=i for (j = i; j < length; j++) { //算出以i为左端,j为右端的子列的子列和 sum = 0; for (k = i; k <= j; k++) { sum += List[k]; } //判断当前子列和与当前最大子列和大小 if (sum > max_sum) { max_sum = sum; } } } return max_sum; }
-
时间复杂度:T(N)=O( N 3 N^3 N3)
算法二:穷举法的优化
-
算法描述
依旧需要算出所有的子列和,但是在算以List[i]为起点,List[j]为终点的子列和的时候,可以依据计算的上一个子列(以List[i]为起点,List[j-1]为终点)的和来计算,即:当前子列和 = 上一个子列和 + List[j]。
-
代码示例
int MaxSubseqSum2(int *List, int length) { int i = 0, j = 0, k = 0; int sum = 0, max_sum = 0; //算出所以子列的和并选出最大一个子列和 //i表示子列的左端 for (i = 0; i < length; i++) { //j表示子列的右端,j>=i sum = 0; for (j = i; j < length; j++) { //以i为左端,j为右端的子列的子列和 为 以i为左端,j-1为右端的子列的子列和加上List[j] sum += List[j]; //判断当前子列和与当前最大子列和大小 if (sum > max_sum) { max_sum = sum; } } } return max_sum; }
-
时间复杂度:T(N)=O( N 2 N^2 N2)
算法三:递归算法
-
算法描述
把数列分成左右两部分。最大子序列和的位置存在三种情况:
- 完全在左半部分;
- 完全在右半部分;
- 跨越左右两部分。
分别求出左半部分的最大子序列和、右半部分的最大子序列和、以及跨越左右两部分的最大子序列和,三者中的最大者就是该数列的最大子列和。
-
求左、右半部分最大子列和求法:把左、右半部分分别作为新的输入序列通过该算法递归求出。
-
跨越左右两部分的最大子序列和求法:求出左半数列中 包含左半数列最右元素的 所有子列中 和最大的一个,并求出右半数列中 包含右半数列最左元素的 所有子列中 和最大的一个,两者和相加,即是跨越左右两部分的最大子序列和。
-
代码示例
int MaxSubseqSum3(int *List, int length) { int left_sum = 0, right_sum = 0, mid_sum = 0; int max_sum = 0; if (length != 1) { //求左边子列和最大值、右边子列最大值和跨过中间位置的子列和的最大值 //当长度为2的倍数时,左边长度和右边长度一样 if (length % 2 == 0) { left_sum=MaxSubseqSum3(List, length / 2); right_sum=MaxSubseqSum3(List+length/2, length / 2); mid_sum=FindMidSum(List, length / 2, length / 2); } //当长度不为2的倍数时,右边长度比左边长度大1 else { left_sum = MaxSubseqSum3(List, length / 2); right_sum = MaxSubseqSum3(List + length / 2, length / 2+1); mid_sum=FindMidSum(List, length / 2, length / 2 + 1); } //比较left_sum、right_sum、mid_sum三个数最大值,将最大值赋值给max_sum,这就是最大子列和 max_sum = left_sum; if (right_sum > max_sum) { max_sum = right_sum; } if (mid_sum > max_sum) { max_sum = mid_sum; } //返回最大子列和 return max_sum; } else { return List[0]; } }
其中找跨越左右两部分的最大子序列和的函数
FindMidSum()
为:int FindMidSum(int *List, int length1, int length2) { int mid_left_max_sum = 0,mid_right_max_sum = 0,sum=0; int i = 0; //求出左半数列中 包含左半数列最右元素的 所有子列中 和最大的一个 mid_left_max_sum = List[length1 - 1]; for (i = length1 - 1; i >= 0; i--) { sum += List[i]; if (sum > mid_left_max_sum) { mid_left_max_sum = sum; } } //求出右半数列中 包含右半数列最左元素的 所有子列中 和最大的一个 sum = 0; mid_right_max_sum = List[length1]; for (i = length1; i < length2+length1; i++) { sum += List[i]; if (sum > mid_right_max_sum) { mid_right_max_sum = sum; } } //返回两个最大值相加的值,即跨越左右两部分的最大子序列和 return mid_left_max_sum + mid_right_max_sum; }
-
时间复杂度:T(N)=N*log(N)
算法四:在线处理算法
-
算法描述
遍历整个数组,扫描时将扫描点累加,并将累加值与最大子列和比较,如果大于最大子列和,这更新最大子列和。如果累加之后结果为负数,将累加和置零,并从下一扫描点开始累加,因为负数与后面的扫描点的值相加,并不能使累加和变得更大。
在线 的意思是指每输入一个数据就进行 即是处理 ,在任何一个地方终止输入,算法都能能正确给出当前解。
-
代码示例
int MaxSubseqSum4(int *List, int length) { int max_sum = 0,sum=0; int i = 0; for (i = 0; i < length; i++) { //向右累加 sum += List[i]; //发现更大的和则更新当前结果 if (sum > max_sum) { max_sum = sum; } //如果当前子列和为负则不可能使后面的部分和增大,抛弃之 if (sum < 0) { sum = 0; } } return max_sum; }
-
时间复杂度:T(N)=O(N)
测试代码
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include"MaxSubseqSum.h"
clock_t start = 0, stop = 0;
double duration = 0;
int main(void)
{
int i = 0;
int max_sum = 0;
int length = 5000;
int *List = NULL;
//随机生成长度为length的数组
List = (int *)malloc(sizeof(int)*length);
srand(time(NULL));
for (i = 0; i < length; i++)
{
List[i] =rand() % length-length/2;
/*printf("%d ", List[i]);*/
}
//算法一所用的时间
start = clock();
for (i = 0; i < 10; i++)
{
max_sum = MaxSubseqSum1(List, length);
}
stop = clock();
duration = (double)(stop - start) / CLK_TCK/10;
printf("\n算法一所用时间:%lf\n最大值为:%d\n\n", duration,max_sum);
//算法二所用的时间
start = clock();
for (i = 0; i < 10; i++)
{
max_sum = MaxSubseqSum2(List, length);
}
stop = clock();
duration = (double)(stop - start) / CLK_TCK / 10;
printf("算法二所用时间:%lf\n最大值为:%d\n\n", duration, max_sum);
//算法三所用的时间
start = clock();
for (i = 0; i < 10; i++)
{
max_sum = MaxSubseqSum3(List, length);
}
stop = clock();
duration = (double)(stop - start) / CLK_TCK / 10;
printf("算法三所用时间:%lf\n最大值为:%d\n\n", duration, max_sum);
//算法四所用的时间
start = clock();
for (i = 0; i < 10; i++)
{
max_sum = MaxSubseqSum4(List, length);
}
stop = clock();
duration = (double)(stop - start) / CLK_TCK / 10;
printf("算法四所用时间:%lf\n最大值为:%d\n\n", duration, max_sum);
free(List);
return 0;
}