1. 程式人生 > 實用技巧 >第二章-時間/空間複雜度

第二章-時間/空間複雜度

演算法(Algorithm)是指用來操作資料、解決程式的一組方法。對於同一個問題,使用不同的演算法,也許最終得到的結果是一樣的,但在過程中消耗的資源和事件卻會有很大的區別。我們通過 時間複雜度空間複雜度 來衡量不同演算法之間的優劣。

時間複雜度: 是指執行當前演算法所消耗的時間

空間複雜度: 是指執行當前演算法需要佔用多少記憶體空間

時間複雜度和空間複雜度只是 定性描述該演算法的執行時間和空間 ,演算法執行的時間和空間的一個 趨勢 ,並不是一個實際的數值。

時間複雜度

常見的時間複雜度量級有:

  • 常數階 O(1)
  • 對數階 O(logN)
  • 線性階 O(n)
  • 線性對數階 O(nlogN)
  • 平方階 O(n^2)
  • 立方階 O(n^3)
  • K次方階 O(n^k)
  • 指數階 O(2^n)

上面從上到下依次的時間複雜度越來越大,執行效率越來越低。

1. 常數階 O(1)

無論程式碼執行了多行,只要沒有迴圈等複雜結構,那這個程式碼的複雜度就是 O(1)

let i = 123; 
let j = 456;
console.log(i, j);

上述程式碼在執行的時候,它消耗的時間並不隨著某個變數的增長而增長,永遠只執行 1 次;無論這段程式碼有多長,都可以使用 O(1) 來表示它的時間複雜度

2. 線性階 O(n)

單層 迴圈體內,程式碼會隨著每次迴圈執行,它消耗的時間是隨著n的變化而變化的,因此這是一個線性相關趨勢,時間複雜度為 O(n)。

for (let i = 0; i < n; i++) {
   	console.log(i)
}

如果一個時間複雜度為 常數階 O(1) 的演算法 和 時間複雜度為 O(n) 的演算法同時存在,那麼整個時間複雜度為 O(1) + O(n) == O(n); 由於時間複雜度本身就是一個趨勢,所以以時間複雜度增長最快的為準

3. 對數階 O(logN)

檢視以下程式碼:

let i = 1;
while (i < n) {
    i = i * 2;
}

從上面程式碼可以看到,在while迴圈裡面,每次都將 i 乘以 2,乘完之後,i 距離 n 就越來越近了。我們試著求解一下,假設迴圈x次之後,i 就大於 2 了,此時這個迴圈就退出了,也就是說 2 的 x 次方等於 n,那麼 x = log2(n)
也就是說當迴圈 log2(n) 次以後,這個程式碼就結束了。因此這個程式碼的時間複雜度趨勢為:O(logn)

4. 線性對數階O(nlogN)

線性對數階O(nlogN) 其實非常容易理解,將時間複雜度為O(logn)的程式碼迴圈N遍的話,那麼它的時間複雜度就是 n * O(logN),也就是了O(nlogN)。

就拿上面的程式碼加一點修改來舉例:

for (let i = 0; i < n; i++) {
   	let m = 1;
    while(m < n) {
        i = i * 2;
    }
}

5. 平方階O(n^2)

將 O(n) 的程式碼再迴圈巢狀一遍,它的時間複雜度就是 O(n^2)

for (let i = 0; i < n; i++) {
   	for (let j = 0; i < n; i++) {
        console.log(i, j)
    }
}

這段程式碼其實就是嵌套了2層n迴圈,它的時間複雜度就是 O(n*n),即 O(n²)
如果將其中一層迴圈的n改成m,即:

for (let i = 0; i < m; i++) {
   	for (let j = 0; i < n; i++) {
        console.log(i, j)
    }
}

那麼它的時間複雜度就變成了 O(m*n)

6. 立方階O(n³)K次方階O(n^k)

參考上面的O(n²) 去理解就好了,O(n³)相當於三層n迴圈,其它的類似

空間複雜度

空間複雜度是對一個演算法在執行過程中臨時佔用儲存空間大小的一個度量,同樣反映的是一個趨勢。

空間複雜度比較常用的有 O(1)、O(n)、O(n^2)

1. 空間複雜度 O(1)

如果演算法執行所需要的臨時空間不隨著某個變數 n 的大小而變化,即此演算法空間複雜度為一個常量,可表示為 O(1);

let i = 1;
let j = 2;
++i;
j++;
let m = i + j;

程式碼中的 i, j, m 所分配的空間都不隨著資料量變化,因此它的空間複雜度為 O(1)

2. 空間複雜度 O(n)

const list = [];
for (let i = 0; i < n; i++) {
    list.push(i);
}    

這段程式碼中,定義了一個數組,併為這個陣列添加了 n 個數據,因此這個陣列佔用的大小為 n。因此這段程式碼的空間複雜度為 O(n)

3. 空間複雜度 O(n^2)

const matrix = []
for (let i = 0; i < n; i++) {
    matrix.push([]);
    for (let j = 0; i < n; j++) {
        matrix[i].push(j);
    }
}

上面程式碼中,開始定義了一個數組,而後使用 for 迴圈再次定義了一個數組,相當於建立了一個二維陣列,這個陣列中的每一項都是一個空間度量為 n 的陣列,因此它的空間複雜度為 O(n^2)

思考題

  1. 如果一段程式碼中有 3 個迴圈,他們迴圈的次數都是 n, 那麼這段程式碼的時間複雜度是 O(3n) 還是 O(n)?
    • 分以下幾種情況:
    • 如果是 3 個並列迴圈的程式碼,那麼總的時間複雜度趨勢為 O(n)
    • 如果是 3 個迴圈巢狀,那麼總的時間複雜度趨勢為 O(n^3)
  2. 假設每天睡覺前,你都會數 2 的次方,1、2、4、8……,每次都數到 n 才睡著,那麼你數了幾個數?時間複雜度是多少?
    • 數了:2^x = n ==> x = log2(n) ; x 從 0 算起 所以數了 log2(n) + 1 個數
    • 時間複雜度的趨勢為 O(logN)

參考:https://zhuanlan.zhihu.com/p/50479555