如何用JavaScript學習演算法複雜度
概述
在本文中,我們將探討 “二次方” 和 “n log(n)” 等術語在演算法中的含義。
在後面的例子中,我將引用這兩個陣列,一個包含 5 個元素,另一個包含 50 個元素。我還會用到javascript中方便的performance API來衡量執行時間的差異。
const smArr = [5,3,2,35,2]; const bigArr = [5,5,http://www.cppcns.com35,2];
什麼是 Big O 符號?
Big O 表示法是用來表示隨著資料集的增加,計算任務難度總體增長的一種方式。儘管還有其他表示法,但通常 big O 表示法是最常用的,因為它著眼於最壞的情況,更容易量化和考慮。最壞的情況意味著完成任務需要最多的操作次數;如果你在一秒鐘內就能恢復打亂魔方,那麼你只擰了一圈的話,不能說自己是做得最好的。
當你進一步瞭解演算法時,就會發現這非常有用,因為在理解這種關係的同時去編寫程式碼,就能知道時間都花在了什麼地方。
當你瞭解更多有關 Big O 表示法的資訊時,http://www.cppcns.com可能會看到下圖中不同的變化。我們希望將複雜度保持在儘可能低的水平,最好避免超過 O(n)。
O(1)
這是理想的情況,無論有多少個專案,不管是一個還是一百萬個,完成的時間量都將保持不變。執行單個操作的大多數操作都是 O(1)。把資料寫到陣列、在特定索引處獲取專案、新增子元素等都將會花費相同的時間量,這與陣列的長度無關。
const a1 = performance.now(); smArr.push(27); const a2 = performance.now(); console.log(`Time: ${a2 - a1}`); // Less than 1 Millisecond const b1 = performance.now(); bigArr.push(27); const b2 = performance.now(); console.log(`Time: ${b2 - b1}`); // Less than 1 Millisecond
O(n)
在預設情況下,所有的迴圈都是線性增長的,因為資料的大小和完成的時間之間存在一對一的關係。所以如果你有 1,000 個數組項,將會花費的 1,000 倍時間。
const a1 = perhttp://www.cppcns.comformance.now(); smArr.forEach(item => console.log(item)); const a2 = performance.now(); console.log(`Time: ${a2 - a1}`); // 3 Milliseconds const b1 = performance.now(); bigArr.forEach(item => console.log(item)); const b2 = performance.now(); console.log(`Time: ${b2 - b1}`); // 13 Milliseconds
O(n^2)
指數增長是一個陷阱,我們都掉進去過。你是否需要為陣列中的每個專案找到匹配對?將迴圈放入迴圈中是一種很好的方式,可以把 1000 個專案的陣列變成一百萬個操作搜尋,這將會使你的瀏覽器失去響應。與使用雙重巢狀迴圈進行一百萬次操作相比,最好在兩個單獨的迴圈中進行 2,000 次操作。
const a1 = performance.now(); smArr.forEach(() => { arr2.forEach(item => console.log(item)); }); const a2 = performance.now(); console.log(`Time: ${a2 - a1}`); // 8 Milliseconds const b1 = performance.now(); bigArr.forEach(() => { arr2.forEach(item => console.log(item)); }); const b2 = performance.now(); console.log(`Time: ${b2 - b1}`); // 307 Milliseconds
O(log n)
我認為關於對數增長最好的比喻,是想象在字典中查詢像 “notation” 之類的單詞。你不會在一個詞條一個詞條的去進行搜尋,而是先找到 “N” 這一部分,然後是 “OPQ” 這一頁,然後按字母順序搜尋列表直到找到匹配項。
通過這種“分而治之”的方法,找到某些內容的時間仍然會因字典的大小而改變,但遠不及 O(n) 。因為它會在不檢視大部分資料的情況下逐步搜尋更具體的部分,所以搜尋一千個專案可能需要少於 10 個操作,而一百萬個專案可能需要少於 20 個操作,這使你的效率最大化。
在這個例子中,我們可以做一個簡單的快速排序。
const sort = arr => { if (arr.length < 2) return arr; let pivot = arr[0]; let left = []; let right = []; for (let i = 1,total = arr.length; i < total; i++) { if (arr[i] < pivot) left.push(arr[i]); else right.push(arr[i]); }; return [ ...sort(left),pivot,...sort(right) ]; }; sort(smArr); // 0 Milliseconds sort(bigArr); // 1 Millisecond
O(n!)
最糟糕的一種可能性是析因增長。最經典的例子就是旅行的推銷員問題。如果你要在很多距離不同的城市之間旅行,如何找到在所有城市之間返回起點的最短路線?暴力方法將是檢查每個城市之間所有可能的路線距離,這是一個階乘並且很快就會失控。
由於這個問題很快會變得非常複雜,因此我們將通過簡短的遞迴函式演示這種複雜性。這個函式會將一個數字去乘以函式自己,然後將數字減去1。階乘中的每個數字都會這樣計算,直到為 0,並且每個遞迴層都會把其乘積新增到原始數字中。
階乘只是從 1 開始直至該數字的乘積。那麼6!是1x2x3x4x5x6 = 720。
const factorial = n => { let num = n; if (n === 0) return 1 for (let i = 0; i < n; i++) { num = n * factorial(n - 1); }; return num; }; factorial(1); // 2 Milliseconds factorial(5); // 3 Milliseconds factorial(10); // 8www.cppcns.com5 Milliseconds factorial(12); // 11,942 Milliseconds
我原本打算顯示factorial(15),但是 12 以上的值都太多,並且使頁面崩潰了,這也證明了為什麼需要避免這種情況。
結束語
我們需要編寫高效能的程式碼似乎是一個不爭得事實,但是我敢肯定,幾乎每個開發人員都建立過至少兩重甚至三重巢狀迴圈,因為“它確實有效”。Big O 表示法在表達和考慮複雜性方面是非常必要的,這是我們從未有過的方式。
以上就是XZvZdTn如何用javaScript學習演算法複雜度的詳細內容,更多關於js演算法複雜度的資料請關注我們其它相關文章!