最大子序列和問題
最大子列和問題是很是經典的問題,基本上講算法的書都會將這個例子,用此例題來說解算法時間複雜度的重要性,對比不一樣算法的時間複雜度。最大子列和問題以下:給定整數序列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]被讀入並被處理,它就再也不須要被記憶。不只如此,在任意時刻,算法都可以對它已經讀入的數據給出正確的答案。具備這種特性的算法叫作聯機算法。僅須要常量的空間並以線性時間運算的聯機算法集合是完美的算法。