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; }
這裡輸入幾個數字做測試,大致記錄下結果作對比。
當然每次測試同一個數字的計算時間不一定都相同,跟當前測試的電腦硬體配置,和其他應用同開有一定關係。
當輸入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...") } } }
常見的時間複雜度所耗時間的大小排列如下:
3、掌握了大O推導法,我們用大O表示法來論證本文最開始計算裴波那契數列的兩種演算法的區別:
3.1 遞迴方式演算法,先看下圖:
可以得出,這裡的遞迴方式演算法用大O表示法來表示,它屬於指數階,可以粗略表示為:O(n) = 2^n
3.2 而第二種迴圈方式演算法為線性階的,用大O表示法為:O(n) = n
3.3 我們對比一下指數階和線性階的函式曲線圖就知道,n係數越大,這兩種方式的最後的結果相差越大。
所以當n越大時,遞迴方式演算法的計算次數呈指數級上升,最後次數結果越大,時間損耗數也就越多。