1. 程式人生 > >快速掌握演算法時間複雜度與空間複雜度

快速掌握演算法時間複雜度與空間複雜度

# 前言 一個演算法的優劣好壞,會決定一個程式執行的時間、空間。也許當小資料量的時候,這種影響並不明顯,但是當有巨量資料的時候,演算法的好壞帶來的效能差異就會出天差地別。可以說直接影響了一個產品的高度和廣度。每個程式設計師都想用最優的演算法解決問題,我們期待自己寫出的程式碼是簡潔、高效的。但是如何評判一個演算法的好壞呢?時間複雜度和空間複雜度就是一個很好的標準。 # 1. 時間複雜度 ## 1.1 概念 執行演算法所需要的計算工作量就是我們常說的時間複雜度。該值不一定等於接下來要介紹的基本執行次數。是一個大約的數值,具有統計意義。 ## 1.2 基本執行次數T(n) 根據計算,得出的該演算法在輸入資料量為n時的,實際執行次數。該值為準確的,具體的數值,有數學意義。 ## 1.3 時間複雜度 根據基本執行次數,去除係數、常數項等得到的漸進時間複雜度。用大O表示法。也就是說,隨著資料量的劇增,不同常數項和係數,已經大致不能夠影響該演算法的基本執行次數。常數項和係數對於計算時間複雜度無意義 ## 1.4 舉例說明 1. T(n) = 2: 該函式總共執行兩條語句,所以基本執行次數為2;時間複雜度為O(1): 該函式的基本執行次數只有常數項,所以時間複雜度為O(1) ``` void test(int n) { int a; a = 10; } ``` 2. T(n) = 2n: 該函式共迴圈n次,每次執行2條語句,所以基本執行次數為2n。時間複雜度捨棄係數,為O(n) ``` void test(int n) { int cnt; for (cnt = 0; cnt < n; cnt++) { int a; a= 10; } } ``` 3. T(n) = 2 * (1 + 2 + 3 + 4 + ... + n) + 1 = 2 * (1 + n) * n / 2 + 1 = n^2 + n + 1。因為共執行(1 + 2 + 3 + 4 + ... + n) 次迴圈,每次迴圈執行2條語句,所有迴圈結束後,最後又執行了1條語句,所以執行次數如上;時間複雜度為O(n^2),因為n和常數項1忽略,它們在資料量劇增的時候,對於執行次數曲線幾乎沒有影響了 ``` void test(int n) { int cnt1, cnt2; for (cnt1 = 0; cnt1 < n; cnt1++) { for (cnt2 = cnt1; cnt2 < n; cnt2++) { int a; a = 10; } } a = 11; } ``` 4. T(n) = 2 * logn 因為每次迴圈執行2條語句,共執行logn次迴圈;時間複雜度為O(logn),忽略掉係數2 ``` void test(int n) { int cnt; for (cnt = 1; cnt < n; cnt *= 2) { int a; a = 10; } } ``` 5. T(n) = n * logn * 2 因為每次迴圈2條語句,共執行n * logn次迴圈;時間複雜度為O(nlogn),忽略掉係數2 ``` void test(int n) { int cnt1, cnt2; for (cnt1 = 0; cnt1 < n; cnt1++) { for (cnt2 = 1; cnt2 < n; cnt2 *= 2) { int a; a = 10; } } } ``` 6. T(n) = 2 * n^3 因為每次迴圈2條語句,共執行n^3 次迴圈;時間複雜度為O(n^3),忽略掉係數2 ``` void test(int n) { int cnt1, cnt2, cnt3; for (cnt1 = 0; cnt1 < n; cnt1++) { for (cnt2 = 0; cnt2 < n; cnt2++) { for (cnt3 = 0; cnt3 < n; cnt3++) { int a; a = 10; } } } } ``` 7. 斐波那契數列的遞迴實現,每次呼叫該函式都會分解,然後要再呼叫2次該函式。所以時間複雜度為O(2^n) ``` int test(int n) { if (n == 0 || n == 1) { return 1; } return (test(n-1) + test(n-2)); } ``` ## 1.5 時間複雜度比較 O(1) < O(log2n) < O(n) < O(nlog2n) < O(n^2) < O(n^3) < O(2^n) < O(n!) < O(n^n) # 2. 空間複雜度 ## 2.1 概念 一個演算法所佔用的儲存空間主要包括: - 程式本身所佔用的空間 - 輸入輸出變數所佔用的空間 - 動態分配的臨時空間,通常指輔助變數 輸入資料所佔空間只取決於問題本身,和演算法無關。我們所說的空間複雜度是對一個演算法在執行過程中臨時佔用儲存空間大小的量度,即第三項。通常來說,只要演算法不涉及到動態分配的空間以及遞迴、棧所需的空間,空間複雜度通常為0(1)。 ## 2.2 舉例說明 1. S(n) = O(1).空間複雜度為O(1),因為只有a, b, c, cnt四個臨時變數。且臨時變數個數和輸入資料規模無關。 ``` int test(int n) { int a, b, c; int cnt; for (cnt = 0; cnt < n; cnt++) { a += cnt; b += a; c += b; } } ``` 2. S(n) = O(n).空間複雜度為O(n),因為每次遞迴都會建立一個新的臨時變數a。且共遞迴n次。 ``` int test(int n) { int a = 1; if (n == 0) { return 1; } n -= a; return test(n); } ``` # 3. 函式的漸進增長與漸進時間複雜度 在上面的例子中,我們通常都會捨棄掉係數和常數項。這是因為當輸入量劇增,接近正無窮時,係數和常數項已經不能夠影響執行次數曲線。不同的係數和常數項曲線會完全重合。我做了一個折線圖用來比較當輸入值n激增時,n^2 曲線和 2n^2 + 100 曲線。可以看到,當資料量劇增時,係數和常數項對於統計時間複雜度都不再有意義,兩條曲線幾乎完全重合。 ![](https://img2020.cnblogs.com/blog/1061082/202004/1061082-20200407233212475-1089707261.png) # 4. 不同演算法的時間複雜度 & 空間複雜度 下圖是我做的一個表格,整理了不同的排序演算法的時間複雜度和空間複雜度供大家參考: ![](https://img2020.cnblogs.com/blog/1061082/202004/1061082-20200407233220344-2124809133.png) ## 感謝大家的閱讀,大家喜歡的請幫忙點下推薦。後面會繼續出精彩的內容,敬請期待! *** **敬告:** **本文原創,歡迎大家學習轉載** ==轉載請在顯著位置註明:== **博主ID:CrazyCatJack** **原始博文連結地址:[https://www.cnblogs.com/CrazyCatJack/p/12657097.html](https://www.cnblogs.com/CrazyCatJack/p/12657097.html)** ***
CrazyCatJack<