1.1 演算法評估標準
1.2 Time complexity
1.2.1 Constant Time O(1)
1.2.2 Logarithmic Time O(log(n))
1.2.3 Linear Time O(n)
1.2.4 Log Linear Time O(n log (n))
1.2.5 Exponential Time O(n^2)
1.2.6 總結
1.3 Space complexity
1.4 時間複雜度和空間複雜度的 Trade-off
二、References
一、前導知識
1.1 演算法評估標準
- Time complexity (時間複雜度)
- Space complexity (空間複雜度)
1.2 Time complexity
Time complexity 是電腦執行演算法所需要耗費的時間成本,通常會用 O (Big O notation) 去計算。
Big O notation 是解決一個規模為 n 的問題所花費的時間,或者所需步驟之數目;而演算法多快通常不是以秒而是步驟次數來衡量,因為每個人電腦效能會影響執行速度,若用秒數來衡量會顯得不夠客觀。
1.2.1 Constant Time O(1)
(念 Big O one)
function sum(a, b) { | |
return a + b; // Return 結果,執行 1 次 | |
} |
這個範例中只有一個步驟,就是計算並回傳 a + b 的值,所以不管 a 或 b 有多大都不影響執行的步驟數目,執行時間不會隨著輸入資料量的增加而增加,故 Time complexity 為 O(1)。
可以稍微簡化成 : 沒用到任何 loop 所以 Time complexity 為 O(1)。
1.2.2 Logarithmic Time O(log(n))
(念 Big O log n)
通常可以能夠對半處理的都屬於 O(log n) 的演算法,比如 binary search;假設 n 是 8,而 log 8 = 3,所以這個演算法會跑 3 次;假設 n 是 9,則這個演算法會跑 4 次。
function powerOf2(n) { | |
let x = 0; // Assign 數值給變數 ,執行 1 次 | |
while (n > 1) { | |
n = n / 2; // 4,執行 2 次;8,執行 3 次 ... n,執行 log n 次 | |
x++; // 4,執行 2 次;8,執行 3 次 ... n,執行 log n 次 | |
} | |
return x; // Return 結果,執行 1 次 | |
} | |
總共需要執行:1 + 2*log n + 1 = 2*log n + 2,忽略常數的係數,故這裡的 Time complexity 會是 O(log (n))。
1.2.3 Linear Time O(n)
(念 Big O n)
function listSum(n) { | |
let result = 0; // Assign 數值給變數,執行 1 次 | |
for (let i = 0; i < n.length; i++) { // Loop array 執行 n 次 | |
result += n[i]; | |
} | |
return result; // Return 結果,執行 1 次 | |
} |
所以執行次數是 1 + n + 1 = n + 2,但如果在 n 相當大的時候,2 就不是那麼重要了,所以我們在比較的時候可只比較最高次方就好,忽略常數的係數,故這裡的 Time complexity 會是 O(n)。
1.2.4 Log Linear Time O(n log (n))
(念 Big O n log n)
時間複雜度為 O(n log (n)) 的演算法,代表著執行時間會隨著以二為底的 log n 再乘上 n 成長。最常見的例子是 Merge Sort、Quick Sort 和 Heap Sort。
let x = n; // Assign 數值給變數 ,執行 1 次 | |
while (x > 0) { | |
let y = x; // Assign 數值給變數 ,執行 1 次 | |
while (y > 0) { // 假設 n = 4,執行 2 次;假設 n = 8,執行 3 次 ... n,執行 logn 次 | |
y = Math.floor(y / 2); | |
} // 整段迴圈會因為 n 的改變而增加次數,是 n log (n) | |
x = x - 1; // Assign 數值給變數 ,執行 1 次 | |
} |
總共需要執行:1 + 1 + n log (n) + 1,忽略常數的係數,故這裡的 Time complexity 會是 O(n log (n))。
1.2.5 Exponential Time O(n^2)
(念 Big O n square / quadratic)
function TwoListsSum(m, n) { | |
for (let i = 0; i < m.length; i++) { // Loop array 執行 m 次 | |
for (let j = 0; j < n.length; j++) { // Loop array 執行 n 次 | |
console.log(m[i] * n[j]); // 執行 loop i 的 m 次 * loop j 的 n 次 = m*n 次 | |
} | |
} | |
} |
若此題假設 m === n,則總共需要執行:m + n + m*n = n + n + n*n = 2n + n^2,一樣忽略常數的係數,故這裡的 Time complexity 會是 O(n^2)。
若此題假設 m !== n,則總共需要執行:m + n + m*n 這裡的 Time complexity 會是 O(m*n)。
可以稍微簡化成 :
一層 loop ⇒ O(n)
兩層 loop ⇒ O(n^2)
…
x 層 loop ⇒ O(n^x) → 念 Big O n to the x
1.2.6 總結
(https://www.youtube.com/watch?v=47GRtdHOKMg&ab_channel=DataDaft)
由圖可看出來 O(n^2)、 O(n^3) … O(n^x)、O(2^n)、O(n!) 的 Time complexity 比其他高很多,尤其是當 input data 增加時,時間更是大幅的增加;所以通常希望演算法的 Time complexity 最佳到最差的排序是 O(1), O(log(n)), O(n), O(n log(n)), O(n^2), O(n^3) … O(n^n), O(2^n), O(n!)
題外話,在刷題時是用 Time complexity 去比較,而實務上要採用哪種方式則是要看需求。
Time complexity 是理論上程式執行的時間,而 Running time 是實務上程式執行的時間。Time complexity 低不代表 Running time 就一定低。
--
前面有說到一些簡單的判斷 Time complexity 的方式,比如說有沒有 for loop,但不是都單靠這些就可以正確的判斷,比如說以下這個範例 :
for (let i = 0; i < s.length; i++) { | |
if (s.indexOf(s[i]) === s.lastIndexOf(s[i])) { | |
return i; | |
} | |
} | |
return -1; |
他雖然只有用到一個 for loop,但是裡面用到了 indexOf 和 lastIndexOf 會去搜尋整個 input 的數值,所以這個範例的 Time complexity 其實會是 O(n^2) 而不是 O(n),在使用和計算上還是要小心注意。
1.3 Space complexity
計算方式與 Time complexity 相似,也是使用 Big O notation 來表示其複雜度。
const add = (n) => { | |
let total = ; | |
for(let i=0; i<n; i++){ | |
total += i; | |
} | |
return total; | |
} |
const createArray = (n) => { | |
const arr = []; | |
for (let i = 0; i < n; i++) { | |
arr.push(i); | |
} | |
return arr; | |
}; |
這裡建立的變數 arr 的長度會根據使用者輸入的 n 增長,因此 Space complexity 為 O(n)。
可以稍微簡化成 :
不用任何 array ⇒ O(1)
若是兩個 array 則是 O(n) + O(n) = O(2n) = O(n)
...
若是 X 個 array 則是 O(n) + O(n) …. + O (n) = O(xn) = O(n)
一維 array ⇒ O(n)
二維 array ⇒ O(n^2)
…
X 維 array ⇒ O(n^X)
let arr = [1,2,3,4,5] | |
// 常見計算法 | |
let total = 0; | |
for (let i = 0; i < arr.length; i++) { | |
total += arr[i]; | |
} | |
// 用 recursion 方式計算 | |
const recursion = (arr, n) => { | |
if (n <= 0) return 0; | |
const x = arr[n - 1]; | |
return recursion(arr, n - 1) + x; | |
} |
1.4 時間複雜度和空間複雜度的 Trade-off
在某些情況下,可以用記憶體空間來多存取資訊,省去重複運算來節省時間;如果沒有多餘的記憶體可以使用,則可以把需要靠記憶體空間存取的資訊靠重複運算來取得。
二、References
--
相關文章
沒有留言:
張貼留言