時間複雜度 與 空間複雜度
時間複雜度 與 空間複雜度
同一個演算法在不同等級的電腦上跑,效率可能會有所不同,我們可以透過比較科學的方式,就是計算時間複雜度(Time Complexity)與空間複雜度(Space Complexity)來判斷演算法好壞。
時間複雜度
衡量程式執行的速度
介紹
時間複雜度可以使用Big O Notation來展示複雜度的趨勢,Big-Ο代表演算法時間函式的上限(Upper bound),而在最壞的狀況下,演算法的執行時間不會超過Big-Ο。
那該如何計算Big O Notation,有三個規則:
- 常數可忽略不計
- 取最大次方
- Log底數可忽略不計
假設我們要求f(n) = 3n+2這段方程式的時間複雜度,根據上面所述規則,將常數部分去掉,根據漸進分析,在n變得非常大時,常數部分就變得微不足道,於是取得時間複雜度為O(n)。
以下為常見時間複雜度,接下來會一一舉例。
| Big O Notation | 別名 | 常見演算法 |
|---|---|---|
| $O(1)$ | 常數 | 陣列讀取 |
| $O(n)$ | 線性 | Linear search |
| $O(logn)$ | 對數(Logarithmic ) | 二分搜尋法 |
| $O(nlogn)$ | 線性 | 堆積排序,合併排序 |
| $O(n^2)$ | 平方 | 氣泡排序,插入排序 |
| $O(n^3)$ | 三次方 | 三重迴圈枚舉法,高斯消去法求解線性方程組 |
| $O(2^n)$ | 指數 | 費氏數列 |
| $O(n!)$ | 階層 | 排列組合,八皇后問題 |
O(1): Constant time
不管帶入的 n 多大,執行時間不會隨著 n 變大而增加,固為 $O(1)$。
/** |
O(n): Linear time
隨著輸入的增長,算法的完成時間會相應增加,取最大階項n,固為$O(n)$。
/** |
O(log n): Logarithmic time
/** |
可以發現i會是結果與自身反覆相乘,就表明正使用對數算法,能夠對半$(n/2)$處理的通常都屬於 $O(log n)$ 的演算法
假設 n 是 8,迴圈總共會跑 3 次,$n = 2^3$也就是 $\log2^n = 3$ ,將底數忽略,時間複雜度為$O(\log n)$
$O(log n)$基本上意味著時間呈線性增長,而n呈指數增長。因此,如果計算2元素需要一秒鐘,計算4元素則需要2幾秒鐘,計算8元素需要幾3秒鐘,依此類推等等。
O(n^3): Cubic time
此函式接受兩個陣列 A 和 B,並傳回它們相乘的結果。它使用三個巢狀迴圈,最外層回圈遍歷第一個陣列的列,中間層的迴圈遍歷第二個陣列的行,最內層陣列遍歷相應行和列的元素。
以下程式只是個範例,有更有效的演算法來執行矩陣乘法,例如:施特拉森演算法(Strassen)算法)。
function matrixMultiplication(A, B) { |
O(2^n): Exponential time
使用循環遍歷輸入陣列的元素,對於每個元素,它透過將元素添加到每個現有子集來建立一個新子集。每添加一個元素,子集的數量就會加倍。此函式的時間複雜度為 $O(2^n)$,因為它會產生 $2^n$ 個子集,其中 $n$ 是輸入陣列的大小。
function generateSubsets(set) { |
位元操作Bit manipulation改進
時間複雜度保持不變,但通過減少迭代次數和操作次數提高了性能。
function generateSubsetsUsingBitManipulation(set) { |
O(n!): Factorial time
這個函式只要產生了所有的 n 階層的排列,其中 n 是輸入陣列的大小。重要的是要注意這只是一個示例,在實際情況下,有更有效的演算法來生成排列,比如 Heap 演算法。
function generatePermutations(arr) { |
使用Heap演算法改進
使用了Heap的演算法來產生所有的排列,相比於上一個是一種更加高效的方法。它使用遞迴函式,在每次遞迴呼叫中,它產生前 n-1 個元素的所有排列,並將最後一個元素與陣列中的前 n-1 個元素交換。它不在每個遞迴呼叫中使用切片slice或連接concat,這就是它比前一個更有效的原因。
function generatePermutationsUsingHeapsAlgorithm(arr) { |
空間複雜度
程式執行所需的記憶體空間
介紹
時間複雜度與空間複雜度是互相
也就是以時間換取空間,以空間換取時間
以下為常見空間複雜度
| Big O Notation | 介紹 | 常見 |
|---|---|---|
| $O(1)$ | 不隨輸入資料量增加而增加 | 常數空間的演算法 |
| $O(n)$ | 記憶體空間和輸入資料量等同 | 需要儲存輸入資料的演算法 |
| $O(n^2)$ | 記憶體空間是輸入資料量的平方 | 需要儲存兩兩之間關係的演算法 |
| $O(log n)$ | 記憶體空間輸入資料量之間呈對數關係 | 遞迴演算法 |
| $O(n log n)$ | 記憶體空間是輸入資料量乘以log(輸入資料量) |
# |
| $O(n + k)$ | # | 基數排序 |
| $O(2^n)$ | 記憶體空間是 2 的輸入資料量次方 |
# |
$O(1)$ 空間複雜度
函式僅使用恆定數量的記憶體來儲存 sum 變數,而不管 a 和 b 的輸入值如何。因此,這個函式的空間複雜度是$O(1)$。
function addNum(a, b) { |
$O(n)$ 空間複雜度
範例為反轉陣列,儲存反轉陣列的大小與輸入陣列的大小成正比。因此,這個函數的空間複雜度是$O(n)$。
function reverseArray(arr) { |
$O(n^2)$ 空間複雜度
該函數使用一個陣列來儲存輸入陣列中所有可能的元素對。對數為 $n(n-1)/2$ 個,其中 n 是輸入陣列的大小。因此,這個函數的空間複雜度是$O(n^2)$。
function generatePairs(arr) { |
$O(log n)$空間複雜度
該函數使用遞迴對輸入陣列執行二分搜尋法。
該函數僅使用少量記憶體來儲存高低指標(high, low)和中間變數(mid),以及遞迴的呼叫的stack。 由於 stack 的大小與輸入陣列大小的對數成正比,因此該函數的空間複雜度為 $O(log n)$。
function binarySearch(arr, target) { |
$O(n + k)$空間複雜度
基數排序的空間複雜度通常為 $O(n + k)$,其中 n 是輸入陣列中元素的數量,k 是可能的數字或位數。這是因為基數排序使用輔助資料結構(例如計數陣列或buckets)在對元素進行排序時臨時儲存元素。
function radixSort(arr) { |
特別感謝
感謝Howard Gou大大訂正




