最大子序列和问题
最大子列和问题是很是经典的问题,基本上讲算法的书都会将这个例子,用此例题来说解算法时间复杂度的重要性,对比不一样算法的时间复杂度。最大子列和问题以下:给定整数序列A1,A2,A3,A4,...,An(可能存在负数),求A(i)+A(i+1)+........+A(j)的最大值(没法输入公式),请看下图:ios
注:为了方便起见,若是全部的整数均为负数,则最大的子序列和为0算法
算法的运行时间
这个问题之因此有如此的吸引力,主要是由于存在求解它的不少算法,并且这些算法的性能又差别很大。咱们将讨论求解该问题的四种算法。这四种算法的运行时间以下表所示:(算法1是O(N^3),图中写错了)函数
咱们还能够经过函数曲线来对这四种算法的时间复杂度函数进行分析,经过曲线咱们清楚的能够看出O(nlgn)算法时间复杂度是介于O(n^2)的O(n)之间的,固然这也不难证实。在实际的状况中,当咱们采用O(n^2)算法的时候,应该在仔细想一想,可否将算法的时间复杂度优化成O(nlgn),这对算法的性能提高也是很是巨大的,不妨要问,为何不优化为O(n)呢?事实上,O(n)时间复杂度意味着只须要进行一次扫描,就能找到问题的解,在大部分的问题中,这是很是的困难的。性能
O(n^3)算法
#include<iostream> #include<stdio.h> using namespace std; int MaxSubsequenceSum(int a[],int n); int main(){ //int a[6] = {-2, 11, -4, 13, -5, -2}; int a[8] = {4, -3, 5, -2, -1, 2, 6, -2}; printf("%d\n",MaxSubsequenceSum(a,8)); } int MaxSubsequenceSum(int a[],int n){ int ThisSum, MaxSum; MaxSum = 0; for(int i = 0; i < n; i++){ for(int j = i; j < n; j++){ ThisSum = 0; for(int k = i; k <= j; k++){ ThisSum += a[k]; } if(ThisSum > MaxSum){ MaxSum = ThisSum; } } } return MaxSum; }
这是一种O(n^3)的解法,说实话,我是写不来这样高时间复杂度的算法,这个算法重复作了不少的无用的计算,强行将算法复杂化,通过简单的分析,直接能够求 ThisSum += a[k] 语句的次数,就可以得出它的时间复杂度:优化
O(n^2)算法
对上述的算法直接优化,咱们发现最里面的循环是彻底多余的,很过度的消耗了大量的时间,很容易就能获得下面的算法spa
int MaxSubsequenceSum(int a[],int n){ int ThisSum, MaxSum; MaxSum = 0; for(int i = 0; i < n; i++){ ThisSum = 0; for(int j = i; j < n; j++){ ThisSum += a[j]; if(ThisSum > MaxSum){ MaxSum = ThisSum; } } } return MaxSum; }
相信大部分人首想一想到的应该是这个算法把,这个算法性能只能说还行。可是,咱们想到了O(n^2)的时候,应该多思考一下,可否将其转化为O(nlogn)呢?若是能的话,这将会极大的提升算法的性能。设计
O(nlogn)算法
若是没有O(n)算法的话,那么递归的威力就能体现出来了。这个算法采用的是分治策略,分治思想是把所求问题划分红两个大体相等的问题,而后递归的对它进行求解,这是分的思想,治的阶段是将两个子问题的解合并到一块儿,最后获得整个问题的解。
在这个问题中,最大的子序列和可能出如今三处,要么是序列的左半部分,要么是序列的右半部分,要么是跨越输入数据的中间左右部分都有,前面的两种状况能够用递归进行求解,第三种状况的最大子序列和能够经过求出前半部分的最大和以及后半部分的最大和而获得,咱们能够经过下面的例子进行分析:code
这个算法的源码有点复杂,仔细读几遍。递归
int MaxSubSum(int A[], int Left, int Right){ int MaxLeftSum, MaxRightSum; int MaxLeftBorderSum, MaxRightBorderSum; int LeftBorderSum, RightBorderSum; int Center; 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(int i = Center; i >= Left; i--){ LeftBorderSum += A[i]; if(LeftBorderSum > MaxLeftBorderSum){ MaxLeftBorderSum = LeftBorderSum; } } MaxRightBorderSum = 0; RightBorderSum = 0; for(int i = Center+1; i <= Right; i++){ RightBorderSum += A[i]; if(RightBorderSum > MaxRightBorderSum){ MaxRightBorderSum = RightBorderSum; } } return Max3(MaxLeftBorderSum+MaxRightBorderSum,MaxLeftSum,MaxRightSum); } int Max3(int a, int b, int c){ if(a>b){ return a > c ? a : c; }else{ return b > c ? b : c; } } int MaxSubsequenceSum(int a[],int n){ return MaxSubSum(a, 0, n-1); }
时间复杂度分析
有兴趣的同窗能够参考网易公开课:麻省理工学院公开课:算法导论,第三集分治法,讲的很是详细,还有推导过程。图片
O(n)算法
int MaxSubsequenceSum(int a[],int n){ int ThisSum = 0, MaxSum = 0; for(int j = 0; j < n; j++){ ThisSum += a[j]; if(ThisSum > MaxSum){ MaxSum = ThisSum; }else if (ThisSum < 0){ ThisSum = 0; //ThisSum < 0,说明跨越a[j]不能使序列和变大 } } return MaxSum; }
这个算法的效率很是的高,又被称为在线处理算法,算法只须要扫描一遍序列,就能找到最大的子序列和,它的技巧就是一旦A[i]被读入并被处理,它就再也不须要被记忆。不只如此,在任意时刻,算法都可以对它已经读入的数据给出正确的答案。具备这种特性的算法叫作联机算法。仅须要常量的空间并以线性时间运算的联机算法集合是完美的算法。