正式工做也有3年的時間了,想要寫出更加優雅的代碼。前端
因此最近在刷leetcode補充數據結構和算法方面的知識。node
學校裏雖然學過,可是僅僅是有個大概的認識。只有實際工做過幾年之後,纔會明白數據結構和算法的重要性。git
若是是通訊專業出身的同窗,或者是硬件出身的同窗必定知道:對於一個信號,咱們能夠從時域和頻域兩個方面去分析。github
那麼計算機科學或者說軟件開發中的算法怎麼去分析呢?
有兩個衡量優劣的維度:時間複雜度和空間複雜度。算法
在這邊博文中,咱們來好好分析一下時間複雜度。segmentfault
常見時間複雜度類型及代碼分析瀏覽器
把算法的執行時間當作時間複雜度?
這種方式是最爲直觀也是最容易想到的方式。
可是有一個問題,那就是代碼在不一樣性能的機器上運行,以及在不一樣的狀態下運行,會呈現出徹底不一樣的運行時間。
好比說我有一臺內存爲32GB內存的mbp,還有一臺8GB的臺式機,假設其它的硬件條件好比cpu,主板以及機器負載狀態一致。一般狀況下,32GB的內存要比8GB的內存運行更快。並且這種理想狀態下的只有單一變量的狀態也是很難作到的。
因此不能經過計算算法的消耗時間做爲時間複雜度。微信
那咱們一般所說的'時間'複雜度中的'時間'究竟是指什麼呢?前端工程師
聰明的前輩們想到了一種方式:大O表示法。數據結構
大O表示法內部有很是複雜的數學計算邏輯,咱們偷個懶,不去證實公式,把公式用好就很厲害了。
爲何不去證實一下或者演算一遍?
我在大一曾經上過一門叫作高等代數的課,有道題目叫作:請證實1+1=2。
看到這個題目應該知道爲何不深究大O表示法背後的數學了吧。
T(n) = O(f(n))
更多的斐波那契數列時間複雜度的分析能夠查看下文中的:如何理解斐波那契數列的時間複雜度O(2^N)?
理論扯了一大堆了,到精彩絕倫的Show me the code環節了。
先來看一張大O複雜度曲線圖。
如下時間複雜度根據最佳->較好->通常->較差->糟糕的順序排列。
let i = 0; let j = 9; i++; j--; let k = i + j;
代碼分析:
i爲1,j爲10,k爲11。
時間複雜度爲O(1)。
let n = 100; let i = 1; while(i<n){ i = i * 2 }
代碼分析:
i爲128。
n爲100,時間複雜度爲O(log2(100))。
由於Math.log2(100)
≈6.64,因此最終的時間複雜度爲O(6.65)。
let n = 100; let j = 0; for(let i = 0;i<n;i++){ j = i; }
代碼分析:
i爲100,j爲99。
n爲100,時間複雜度爲O(100)。
let n = 100; for(let m = 0; m<n; m++){ let i = 1; while(i<n){ i = i * 2 } }
代碼分析:
i爲128。
m爲100,n爲100,時間複雜度爲O(m log2(n))。
由於100* Math.log2(100)
≈664.39,因此最終的時間複雜度爲O(664.39)。
let n = 100 let v = 0; for(let i =0;i<n;i++){ for(let j = 0; j<n; j++){ v = v+j+i; } }
代碼分析:
v爲990000,i爲100,j爲100.
n爲100,時間複雜度爲O(100^2)。
也就是O(10000)。
立方型O(n^3)、K次方型O(n^k)和平方型O(n^2)相似,無非是多了幾回循環。
// 立方型O(n^3) for(let i =0;i<n;i++){ for(let j = 0; j<n; j++){ for(let m = 0; m<n; m++){ } } } // K次方型O(n^k) for(let i =0;i<n;i++){ for(let j = 0; j<n; j++){ for(let m = 0; m<n; m++){ for(let p = 0; p<n; p++){ ... // for循環繼續嵌套下去,k值不斷增大 } } } }
斐波那契數列(兔子數列、黃金分割數列):一、一、二、三、五、八、1三、2一、34···
題目:leetcode 509 斐波那契數
題解:[509.斐波那契數列 (Fibonacci Number)]https://github.com/FrankKai/l...
/** * @param {number} N * @return {number} */ var fib = function (N) { /** * 解法1: 遞歸 * 性能: 88ms 34.2MB * 時間複雜度:O(2^N) */ if (N <= 1) return N; return fib(N - 1) + fib(N - 2); };
假設N等於100。
代碼分析:
結果爲 xxx。
由於瀏覽器直接卡死。nodejs中也運行不出來。
具體緣由則是2的100次方真的太大了。算不來。
N爲100,時間複雜度爲O(2^100)。
由於Math.pow(2, 100)
= 1.2676506002282294e+30,因此最終的時間複雜度爲O(1.2676506002282294e+30)。大到爆表。
立方底指數型O(3^n)、K次底指數型O(k^n)與平方底指數型O(2^n)相似,只不過基數變爲了3和k。
O(Math.pow(3, n)) O(Math.pow(k, n))
假設n爲100,假設k爲5。
Math.pow(3, n)爲5.153775207320113e+47。
Math.pow(5, n)爲7.888609052210118e+69。
時間複雜度也是巨高,真的是指數爆炸💥。
更多的斐波那契數列時間複雜度O(2^N)的分析能夠查看下文中的:如何理解斐波那契數列的時間複雜度O(2^N)?
function nFacRuntimeFunc(n) { for(let i=0; i<n; i++) { nFacRuntimeFunc(n-1); } }
階乘型O(n!)的時間複雜度按照(n!+(n-1)!+(n-2)!+ ··· + 1)
+((n-1)!+(n-2)!+ ··· + 1)
+ ···
的方式去計算。
注意哦,這裏是多個階乘的和。不只僅是n * (n-1) * (n-2) * (n-3)···1
。
假設n從0到10,它的算法複雜度O(n!)依次爲1,4,15,64,325,1956,13699,109600,986409,9864100···
爲了和上文中的其它算法複雜度作比較,n爲100時是多少呢?
**O(2^n)爲10纔是1024,n爲100時O(2^n)直接瀏覽器卡死了。
O(n!)才爲10就接近1000萬了,真要是n設置成100,計算到機器燒了也計算不出吧。**
因此n爲100時的O(n!)就不要想了,龐大到恐怖的一個數字。
更多的階乘型時間複雜度O(n!)的分析能夠查看下文中的:如何理解階乘型算法複雜度O(n!)?
O(2^N)
Math.pow(base, ex)
,2個遞歸因此base是2。/** * @param {number} N * @return {number} */ var fib = function (N) { /** * 解法1: 遞歸 * 性能: 88ms 34.2MB */ console.log('foo'); if (N <= 1) return N; return fib(N - 1) + fib(N - 2) };
N | 打印foo數 | O(2^N) |
---|---|---|
1 | 1 | O(2^0) |
2 | 2^1 + 1 | O(2^1) |
3 | 2^2 + 1 | O(2^2 ) |
4 | 2^3 + 1 | O(2^3 ) |
5 | 2^4 + 1 | O(2^4 ) |
經過上表咱們分析獲得:
若是包含1的話,嚴格來說時間複雜度是O(2^(N-1))。
若是從N>1開始計算,時間複雜度確實是O(2^N)。
斐波那契數列很是長,N->∞,所以能夠將斐波那契數列的時間複雜度直接看作是O(2^N)。
O(N!)
咱們把上面的代碼改造一下,增長一個count用來統計O(n!)。
let count = 0; function nFacRuntimeFunc(n) { for(let i=0; i<n; i++) { count++; nFacRuntimeFunc(n-1); } }
階乘型O(n!)的時間複雜度按照(n!+(n-1)!+(n-2)!+ ··· + 1)
+((n-1)!+(n-2)!+ ··· + 1)
的方式去計算。
注意哦,這裏是多個階乘的和。不只僅是n * (n-1) * (n-2) * (n-3)···1
。
上述示例中的count即爲複雜度的值。
n | 屢次n! + (n-1)! + ··· + 1! | count | O(n!) |
---|---|---|---|
1 | 1 | 1 | O(1) |
2 | (2!+1!) +(1!) | 4 | O(4) |
3 | (3!+(2!+1!)+1!)+((2!+1!)+1!)+(1!) | 15 | O(15) |
4 | ... | 64 | O(64) |
5 | ... | 325 | O(325) |
6 | ... | 1956 | O(1956) |
7 | ... | 13699 | O(13699) |
8 | ... | 109600 | O(109600) |
9 | ... | 986409 | O(986409) |
10 | ... | 9864100 | O(9864100) |
快看看這個表格吧,n爲10的時候O(n!)達到了O(9864100),接近了O(一千萬)。這種算法的性能真的是糟糕到極致了。
https://juejin.im/post/5e7c09...
https://zhuanlan.zhihu.com/p/...
https://www.bigocheatsheet.com/
https://stackoverflow.com/que...
期待和你們交流,共同進步,歡迎你們加入我建立的與前端開發密切相關的技術討論小組:
- 微信公衆號: 生活在瀏覽器裏的咱們 / excellent_developers
- Github博客: 趁你還年輕233的我的博客
- SegmentFault專欄:趁你還年輕,作個優秀的前端工程師
- Leetcode討論微信羣:Z2Fva2FpMjAxMDA4MDE=(加我微信拉你進羣)
努力成爲優秀前端工程師!