从最大子列和问题看几种不同的算法思想
题目描述如下:
只看题目描述不看测试数据特点的话,第一眼能想到的算法无非就是利用遍历逐个相加,算出每一种可能的子列和,然后返回其中最大的子列和,看看代码如何实现
int MaxSumSeq(int a[],int len){ int ThisSum=0,MaxSum=0; for(int i=0;i<len;i++){//子列的左端 for(int j=i;j<len;j++){//子列的右端,循环实际上是左端一个个加,到右端为止,然后再移动左端 ThisSum+=a[i]; if(ThisSum>MaxSum) MaxSum=ThisSum; } } return MaxSum; }
这个算法应该是比较容易想到的(不过小白笔者还是写了一小会555555),但这个代码的时间复杂度高达O(n2),对于测试数据来说,前两个可以还算快的跑完,但是3,4,5的数据量经过平方就变得非常恐怖了,所需要的时间也快速飙升,那么有没有复杂度更低的算法呢
int Max3( int A, int B, int C ) { /* 返回3个整数中的最大值 */ return A > B ? A > C ? A : C : B > C ? B : C; } int DivideAndConquer( int List[], int left, int right ) { /* 分治法求List[left]到List[right]的最大子列和 */ int MaxLeftSum, MaxRightSum; /* 存放左右子问题的解 */ int MaxLeftBorderSum, MaxRightBorderSum; /*存放跨分界线的结果*/ int LeftBorderSum, RightBorderSum; int center, i; if( left == right ) { /* 递归的终止条件,子列只有1个数字 */ if( List[left] > 0 ) return List[left]; else return 0; } /* 下面是"分"的过程 */ center = ( left + right ) / 2; /* 找到中分点 */ /* 递归求得两边子列的最大和 */ MaxLeftSum = DivideAndConquer( List, left, center ); MaxRightSum = DivideAndConquer( List, center+1, right ); /* 下面求跨分界线的最大子列和 */ MaxLeftBorderSum = 0; LeftBorderSum = 0; for( i=center; i>=left; i-- ) { /* 从中线向左扫描 */ LeftBorderSum += List[i]; if( LeftBorderSum > MaxLeftBorderSum ) MaxLeftBorderSum = LeftBorderSum; } /* 左边扫描结束 */ MaxRightBorderSum = 0; RightBorderSum = 0; for( i=center+1; i<=right; i++ ) { /* 从中线向右扫描 */ RightBorderSum += List[i]; if( RightBorderSum > MaxRightBorderSum ) MaxRightBorderSum = RightBorderSum; } /* 右边扫描结束 */ /* 下面返回"治"的结果 */ return Max3( MaxLeftSum, MaxRightSum, MaxLeftBorderSum + MaxRightBorderSum ); } int MaxSubseqSum3( int List[], int N ) { /* 保持与前2种算法相同的函数接口 */ return DivideAndConquer( List, 0, N-1 ); }
那就是用经典的分而治之思想了,将总列分为左右两个子列,分别求他们的子列和与跨越中线的子列和,然后对于左右两边的子列,继续分为子列,继续求子列和和跨越中线的子列和······然后再依次返回其中的最大子列和,相加······这样的算法好处是时间复杂度大大减少,达到了O(nlog n),但当数据非常非常大的时候,可能由于递归次数过多导致栈溢出,程序不正常结束(对于快速排序和求子列和这种分而治之的问题一般达不到溢出的程度)
笔者一度认为这就是最高效的算法了,但今日又看到了新的更快的算法:在线处理
int MaxSumSeq(int a[],int len){ int ThisSum=0,MaxSum=0; for(int i=0;i<len;i++){ ThisSum+=a[i]; if(MaxSum<ThisSum) MaxSum=ThisSum; if(ThisSum<0) ThisSum=0; } return MaxSum; }
可以看到它更加精简,时间复杂度更低,只有O(n),这对比分而治之又是快了不少,那么它的实现原理是什么呢
从第一个数开始相加,当ThisSum小于0时,那么下一个数再怎么大,ThisSum肯定都不是最大子列和了,因为下一个数的值都减少了,就必然不可能时最大子列和了,因此就让ThisSum等于0,相当于重新开始算最大的子列和,只要ThisSum不小于0,那么下一个值加上它无论如何都不会变得更小。
为什么叫他在线处理呢,因为它每一步得到的子列和一定都是当前可以达到的最大的子列和,随着循环的进行,最大子列和不断的更新,循环结束后就得到了最大子列和。
这是笔者首次见到这种思想的算法,似乎只有KMP匹配算法和它有些许相似,不像分而治之的算法以前见过快速排序,希望以后能见到更多这种思想的算法,加深印象,拓宽思路。