演算法之複雜度判斷

TDX發表於2019-05-31

1、演算法:演算法是解決特定問題求解步驟的描述,在計算機中表現為指令的有限序列,並且每條指令表示一個或多個操作。

那麼一個怎樣的演算法才能稱得上是好演算法,也就是說有沒有什麼標準來評判一個演算法的好壞?

在此之前,我們們先來做個試驗:

  用兩種方式來實現求裴波那契數列第n項的值,一種方式用遞迴方式,第二種方式用普通迴圈方式。

  在得到結果之前,你猜猜那種方式計算結果更快一些,還是一樣快?

  測試程式碼如下(JavaScript):

/**
 * 1、遞迴實現斐波那契數列:
 * 1,1,2,3,5...  第n項 = 第n-1項 + 第n-2項
 * */
function recursionFbi(n){
    if (n < 3) return 1;
    if (n == 3) return 2;
    // console.log("遞迴n: ", n);
    return recursionFbi(n-1) + recursionFbi(n-2);
}

/**
 * 2、迴圈實現裴波那契數列
 * 1,1,2,3,5... 第n項 = 第n-1項 + 第n-2項
 * */
function circleFbi(n){

    if (n < 3) return 1;
    var a = 1, b = 1, c = 0;
    for (var i = 0; i < n; i++){
        if (i > 1){
            c = a + b;
            a = b;
            b = c;
        }
        console.log("迴圈i: ", i, ", c: ", c);
    }
    return c;
}
View Code

這裡輸入幾個數字做測試,大致記錄下結果作對比。

當然每次測試同一個數字的計算時間不一定都相同,跟當前測試的電腦硬體配置,和其他應用同開有一定關係。

當輸入n=51的時候,測試結果截圖如下:

還有輸入其他一些n值的統計資料:

   上面通過兩種方式求裴波那契數列,表現出來的時間損耗值相差驚人!為什麼會有這麼大的差別?這體現了一個好的演算法能讓程式的執行效率事半功倍,一個糟糕的演算法對於程式來說簡直就是災難!剛才這兩種演算法方式的區別,等下再分析。

 

2、現在,我們來說說如何來粗略的估算一個演算法的好壞。

  我們對於演算法的設計要求是:正確性、可讀性、健壯性、時間效率高、儲存量低

  因此對於一個演算法,我們在執行前,可以從這五個角度來進行判斷分析,下面主要從時間效率和儲存量角度來細說下:

  • 時間複雜度(time complexity):估算程式指令的執行次數(執行時間)
  • 控制元件複雜度(space complexity):估算所需佔用的儲存空間

  

  大O表示法是一種粗略的分析模型,能幫助我們快速估算一個演算法的執行效率,我們用它來描述演算法的時間複雜度。

  常見的時間複雜度有這些:

  在使用大O推導法時,對於常數階,我們用常數1代替;有多階項則只保留最高階項;最高階項的常數去除。如圖:

  

這裡貼上幾個示例用來練習時間複雜度的計算(JavaScript):

//演算法複雜度:O(n)
function testCount1(n){
    //指令計數:多個if算作一個指令
    if (n > 10){
        console.log("n > 10")
    }
    else if(n > 5){
        console.log("n > 5")
    }
    else{
        console.log("n <= 5")
    }

    //指令計數:1 + n + n + n = 3n + 1
    for (var i = 0; i < n; i++){
        console.log("...testCount1...")
    }
}

//演算法複雜度:O(logn)
function testCount3(n){
    //指令計數:n/2 = log2(n)
    //n=2 -> 1;  n=4->2;    n = 8->3;   n = 16->4;  n = 32->6
    //1=log2(2); 2=log2(4); 3=log2(8);  4=log2(16);  6=log2(32)
    while((n = n / 2) > 0){
        console.log("***testCount3***");
    }
}

//演算法複雜度:O(nlogn)
function testCount4(n){
    //指令計數:1 + 2*log2(n) + log2(n) * (1+3n) = 1 + 3log2(n) + 3n*log2(n)
    for (var i = 1; i < n; i = i * 2){
        //1 + 3n
        for (var j = 0; j < n; j++){
            console.log(">>>testCount4>>>")
        }
    }
}

//演算法複雜度:O(n^2)
function testCount2(n){
    //指令計數:1 + 2n + n * (1+3n) = 1 + 3n + 3n^2 = 3n^2 + 3n + 1
    for (var i = 0; i < n; i++){
        for (var j = 0; j < n; j++){
            console.log("...testCount2...")
        }
    }
}
View Code

 常見的時間複雜度所耗時間的大小排列如下:

 

3、掌握了大O推導法,我們用大O表示法來論證本文最開始計算裴波那契數列的兩種演算法的區別:

 3.1 遞迴方式演算法,先看下圖:

  

  可以得出,這裡的遞迴方式演算法用大O表示法來表示,它屬於指數階,可以粗略表示為:O(n) = 2^n

  3.2 而第二種迴圈方式演算法為線性階的,用大O表示法為:O(n) = n

  3.3 我們對比一下指數階和線性階的函式曲線圖就知道,n係數越大,這兩種方式的最後的結果相差越大。

    所以當n越大時,遞迴方式演算法的計算次數呈指數級上升,最後次數結果越大,時間損耗數也就越多。

 

測試Demo地址:https://github.com/xiaotanit/Tan_DataStruct

相關文章