1. 程式人生 > 實用技巧 >演算法概述和時間複雜度

演算法概述和時間複雜度

什麼是演算法

演算法是用於解決特定問題的一系列的執行步驟,使用不同演算法,解決同一個問題,效率可能相差非常大

比如:求第 n 個斐波那契數(fibonacci number)

/**
     * 斐波那契數列 Fibonacci sequence
     * 斐波那契數列(Fibonacci sequence),又稱黃金分割數列、
     * 因數學家萊昂納多·斐波那契(Leonardoda Fibonacci)以兔子繁殖為例子而引入,故又稱為“兔子數列”,
     * 指的是這樣一個數列:0、1、1、2、3、5、8、13、21、34、……
     * 在數學上,斐波那契數列以如下被以遞推的方法定義:F(1)=1,F(2)=1, F(n)=F(n - 1)+F(n - 2)(n ≥ 3,n ∈ N*)
     
*/ public static int fibonacciSequence_01(int i){ if(i<=1){ return i; } return fibonacciSequence_01(i-1)+fibonacciSequence_01(i-2); } public static int fibonacciSequence_02(int n){ if(n<=1){ return n; } int first=0;
int second=1; for (int i=0;i<n-1;i++){ int sum=first+second; first=second; second=sum; } return second; }

如何評判一個演算法的好壞?

如果單從執行效率上進行評估,可能會想到這麼一種方案 : 比較不同演算法對同一組輸入的執行處理時間 .這種方案也叫做:事後統計法

此方案有比較明顯的缺點 1.執行時間嚴重依賴硬體以及執行時各種不確定的環境因素 2.必須編寫相應的測算程式碼 3.測試資料的選擇比較難保證公正性

還可以從以下維度來評估演算法的優劣

  • 正確性、可讀性、健壯性(對不合理輸入的反應能力和處理能力)
  • 時間複雜度(time complexity):估算程式指令的執行次數(執行時間)
  • 空間複雜度(space complexity):估算所需佔用的儲存空間

大O表示法(Big O)

一般用大O表示法來描述複雜度,它表示的是資料規模 n 對應的複雜度,此方法忽略常數、係數、和低階

◼ 注意:大O表示法僅僅是一種粗略的分析模型,是一種估算,能幫助我們短時間內瞭解一個演算法的執行效率

對數階的細節

log2n = log29 ∗ log9n

所以 log2n 、log9n 統稱為 logn

常見的複雜度

下圖來自維基百科

常數級別O(1)

O(1):演算法複雜度和問題規模無關。換句話說,哪怕你拿出幾個PB的資料,我也能一步到位找到答案。

理論上雜湊表就是O(1)。因為雜湊表是通過雜湊函式來對映的,所以拿到一個關鍵字,用雜湊函式轉換一下,就可以直接從表中取出對應的值。和現存資料有多少毫無關係,故而每次執行該操作只需要恆定的時間

(當然,實際操作中存在衝突和衝突解決的機制,不能保證每次取值的時間是完全一樣的)

對數級別O(logN)

O(logN):演算法複雜度和問題規模是對數關係。換句話說,資料量大幅增加時,消耗時間/空間只有少量增加(比如,當資料量從2增加到2^64時,消耗時間/空間只增加64倍,常見於二分法

int number = 1; // 語句執行一次
while (number < n) { // 語句執行logn次
 // 這裡的2是log的底數
 // 底數在大O符號中是省去的
 number *= 2; // 語句執行logn次
}

線性級別O(N)

O(n):演算法複雜度和問題規模是線性關係。換句話說,隨著樣本數量的增加,複雜度也隨之線性增加

int i =0; // 語句執行一次
while (i < n) { // 語句執行n次
 print(i); //語句執行n次
 i++; // 語句執行n次
}

線性對數級別O(NlogN)

public static void test6(int n) {
        // log5(n)
        // O(logn)
        while ((n = n / 5) > 0) {
            System.out.println("test");
        }
    }

平方級別O(N^2)

O(n^2)計算的複雜度隨著樣本個數的平方數增長。這個例子在演算法裡面,就是那一群比較挫的排序,比如冒泡等等

for (int i = 0; i < n; i++) { // 語句執行n次
 for (int j = 0; j < n; j++) { // 語句執行n^2次
 print('I am here!'); // 語句執行n^2
 }
}

指數級別O(2^N)

如果一個演算法的執行時間是指數級的(exponential),一般它很難在實踐中使用

斐波那契數的例子的第一個演算法就是O(2^N)

有時候演算法之間的差距,往往比硬體方面的差距還要大

演算法的優化方向

1.用盡量少的儲存空間

2.用盡量少的執行步驟(執行時間)

3.根據情況,可以

  • 空間換時間
  • 時間換空間