時間複雜度分析經典問題——最大子序列和

時間複雜度經典問題——最大子列和問題

最大子序列和問題

最大子列和問題是很是經典的問題,基本上講算法的書都會將這個例子,用此例題來說解算法時間複雜度的重要性,對比不一樣算法的時間複雜度。最大子列和問題以下:給定整數序列A1,A2,A3,A4,...,An(可能存在負數),求A(i)+A(i+1)+........+A(j)的最大值(沒法輸入公式),請看下圖:
圖片描述ios

注:爲了方便起見,若是全部的整數均爲負數,則最大的子序列和爲0算法

算法的運行時間

這個問題之因此有如此的吸引力,主要是由於存在求解它的不少算法,並且這些算法的性能又差別很大。咱們將討論求解該問題的四種算法。這四種算法的運行時間以下表所示:(算法1是O(N^3),圖中寫錯了)圖片描述函數

  • 表中的幾個重要的狀況值得注意。對於小量的輸入,算法能夠在眨眼之間的完成,於是若是隻是小量輸入的狀況下,那麼花費大量的時間去設計優秀的算法恐怕是不值得的。另外一方面,隨着業務,用戶的增長,小量輸入的狀況可能會發生變化,哪些低效率的程序可能必需要進行重寫。
  • 其次,表中所給的時間不包括讀入數據的所須要的時間,對於算法4,僅僅從磁盤讀入數據所用的時間極可能在數量級上比求解問題所需的時間還要大。數據的讀入通常是一個瓶頸,一旦數據讀入,問題就會迅速解決。可是對於低效的算法,它必然要耗費大量的計算資源。所以,只要可能,使得算法足夠有效而不至於成爲問題的瓶頸是很是重要的。

咱們還能夠經過函數曲線來對這四種算法的時間複雜度函數進行分析,經過曲線咱們清楚的能夠看出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

  • 前半部分最大子序列和爲6,
  • 後半部分的最大子序列和爲8。
  • 前半部分包含最後一個元素的最大和是4,然後半部分包含第一個元素的的最大和是7,所以跨越兩部分的最大和是11,這是最大的子列和。

這個算法的源碼有點複雜,仔細讀幾遍。遞歸

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]被讀入並被處理,它就再也不須要被記憶。不只如此,在任意時刻,算法都可以對它已經讀入的數據給出正確的答案。具備這種特性的算法叫作聯機算法。僅須要常量的空間並以線性時間運算的聯機算法集合是完美的算法。

相關文章
相關標籤/搜索