演算法概述和時間複雜度
什麼是演算法
演算法是用於解決特定問題的一系列的執行步驟,使用不同演算法,解決同一個問題,效率可能相差非常大
比如:求第 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.根據情況,可以
- 空間換時間
- 時間換空間