1. 程式人生 > >資料結構與算法系列課程之二:複雜度分析(上)

資料結構與算法系列課程之二:複雜度分析(上)

資料結構和演算法,本身就是要解決 “快” 和 “省” 的問題。考量的指標分別就是 “時間複雜度” 和 “空間複雜度”。

時間複雜度表示程式碼執行時間隨著資料規模增長的變化趨勢,也叫漸進時間複雜度。

空間複雜度,全稱漸進空間複雜度,表示演算法的儲存空間資料規模之間的增長關係。

進行復雜度分析的原因:

事後統計法:簡單來說,就是讓程式碼在實際的平臺跑一遍。

事後統計法有以下侷限性:

1,測試結果非常依賴測試環境

2,測試結果受資料規模的影響很大

大O複雜度表示法:

T(n) = O(f(n))

T(n)表示程式碼的執行時間,n表示資料規模的大小,f(n)表示每行程式碼執行的次數總和

時間複雜度分析:

1,只關注迴圈執行次數最多的一段程式碼

程式碼示例:

int cal(int n){
    int sum = 0;
    int i = 1;
    for(; i <= n; ++i){
         sum = sum + i;   
    }
    return sum;
}

第2、3行程式碼都是常量級的執行時間,與n的大小無關,所以對於複雜度並沒有影響。迴圈執行次數最多的是第4、5兩行程式碼。

總的時間複雜度是O(n);

2,加法法則:總複雜度等於量級最大的那段程式碼的複雜度

程式碼示例:

int call(int n){
    int sum_1 = 0;
    int p = 1;
    for(; p < 100; ++p){
        sum_1 = sum_1 + p;
    }

    int sum_2 = 0;
    int q = 1;
    for(; q < 100; ++q){
        sum_2 = sum_2 + q;
    }

    int sum_3 = 0;
    int i = 1;
    int j = 1;
    for(; i <= n; ++i){
        j = 1;
        for(; j <= n; ++j){
            sum_3 = sum_3 + i*j;
        }
    }

    return sum_1 + sum_2 + sum_3;
}

第一段:sum_1部分,屬於常量的執行時間。對於一個執行次數固定的程式碼,即使迴圈10w,100w次,只要是一個已知數,跟n無關,照樣是常量級的執行時間。

第二段和第三段:時間複雜度分別是O(n)和O(n2).

總的時間複雜度等於量級最大的那段程式碼的時間複雜度。上例中:T(n) = O(n);

3,乘法法則:巢狀程式碼的複雜度等於巢狀內外程式碼複雜度的乘積

程式碼示例:

int cal(int n){
    int ret = 0;
    int i= 1;
    for (; i < n; ++i){
        ret = ret + f(i);
    }
}


int f(int n){
    int sum = 0;
    int i = 1;
    for(; i < n; ++i){
        sum = sum + i;
    }
    return sum;
}

以上示例中,cal()中巢狀呼叫了f(),複雜度為O(n)的函式巢狀呼叫複雜度為O(n)的函式,總的複雜度T(n) = T1(n) *T2(n) = O(n*n) = O(n2)。

常見時間複雜度量級(按數量級遞增)

多項式量級:

  • 常量階 O(1)
  • 對數階 O(logn)
  • 線性階 O(n)
  • 線性對數階 O(nlogn)
  • 平方階 O(n2)、立方階 O(n3)、... k次方階 O(nk):n的k次方

非多項式量級:

  • 指數階 O(2n) 2的n次方
  • 階乘階 O(n!) 

以下幾個常見的多項式時間複雜度:

1,常量階 O(1)

執行次數是一個確定數字的程式碼,即執行時間不隨n的增長而增大,和n沒有關係,都記作 O(1)

2, O(logn)、 O(nlogn)

示例程式碼:

i = 1;
while( i <= n ){
    i = i*2;
}

以上程式碼,當2的k次方大於n時,迴圈終止,即執行次數:

k = \log_{2}n

對於O(n\log n),如果一段程式碼的時間複雜度是O(\log n),我們迴圈執行n次,則時間複雜度就是O(n\log n)

3,O(m+n) 、O(m*n)

例項程式碼:

in cal(int m, int n){
    int sum_1 = 0;
    int i = 1;
    for(; i < m; ++i){
        sum_1 = sum_1 +i;
    }

    int sum_2 = 0;
    int j = 1;
    for(; j < n; ++j){
        sum_2 = sum_2 +j;
    }

    return sum_1 + sum_2;
}

以上,無法事先評估 m 和 n 誰的量級大,所以無法利用加法準則,上面程式碼的時間複雜度就是O(m+n)

同理,當 m和n的程式碼出現巢狀,且無法判斷m和n誰的量極大,時間複雜度對應的就是O(m*n)

空間複雜度分析

空間複雜度,全稱漸進空間複雜度,表示演算法的儲存空間資料規模之間的增長關係。

程式碼例項:

void print(int n){
    int i = 0;
    int[] a = new int[n];
    for(i; i < n; ++i){
        a[i] = i * i;
    }

    for(i = n-1; i >= 0; --i){
        print out a[i]
    }
}

以上程式碼中,第二行i對應申請了一個空間儲存變數,屬於常量階,和資料規模n沒有關係,因此可以忽略。

第3行申請了一個大小為n的int 型別陣列,除此之外,剩下的程式碼都沒有佔用更多的空間。

所以整段程式碼的空間複雜度就是O(n)

常見的空間複雜度是O(1) 、O(n)O(n^2),像O(\log n)O(n\log n) 這樣的對數階複雜度平時都用不到。