時間復雜度 - Convert 計算次數 TO 時間復雜度


我們假設計算機運行一行基礎代碼需要執行一次運算。

int aFunc(void) { printf("Hello, World!\n"); // 需要執行 1 次 return 0; // 需要執行 1 次 } 

那么上面這個方法需要執行 2 次運算

int aFunc(int n) { for(int i = 0; i<n; i++) { // 需要執行 (n + 1) 次 printf("Hello, World!\n"); // 需要執行 n 次 } return 0; // 需要執行 1 次 } 

這個方法需要 (n + 1 + n + 1) = 2n + 2 次運算。

我們把 算法需要執行的運算次數 用 輸入大小n 的函數 表示,即 T(n) 。
此時為了 估算算法需要的運行時間 和 簡化算法分析,我們引入時間復雜度的概念。

1-如何把運算次數函數,變為時間復雜度

定義:存在常數 c 和函數 f(N),使得當 N >= c 時 T(N) <= f(N),表示為 T(n) = O(f(n)) 。
如圖:

 

 

 

當 N >= 2 的時候,f(n) = n^2 總是大於 T(n) = n + 2 的,於是我們說 f(n) 的增長速度是大於或者等於 T(n) 的,也說 f(n) 是 T(n) 的上界,可以表示為 T(n) = O(f(n))。

因為f(n) 的增長速度是大於或者等於 T(n) 的,即T(n) = O(f(n)),所以我們可以用 f(n) 的增長速度來度量 T(n) 的增長速度,所以我們說這個算法的時間復雜度是 O(f(n))。

算法的時間復雜度,用來度量算法的運行時間,記作: T(n) = O(f(n))。它表示隨着 輸入大小n 的增大,算法執行需要的時間的增長速度可以用 f(n) 來描述。

顯然如果 T(n) = n^2,那么 T(n) = O(n^2),T(n) = O(n^3),T(n) = O(n^4) 都是成立的,但是因為第一個 f(n) 的增長速度與 T(n) 是最接近的,所以第一個是最好的選擇,所以我們說這個算法的復雜度是 O(n^2) 。

那么當我們拿到算法的執行次數函數 T(n) 之后怎么得到算法的時間復雜度呢?

  1. 我們知道常數項對函數的增長速度影響並不大,所以當 T(n) = c,c 為一個常數的時候,我們說這個算法的時間復雜度為 O(1);如果 T(n) 不等於一個常數項時,直接將常數項省略。
比如
第一個 Hello, World 的例子中 T(n) = 2,所以我們說那個函數(算法)的時間復雜度為 O(1)。
T(n) = n + 29,此時時間復雜度為 O(n)。
  1. 我們知道高次項對於函數的增長速度的影響是最大的。n^3 的增長速度是遠超 n^2 的,同時 n^2 的增長速度是遠超 n 的。 同時因為要求的精度不高,所以我們直接忽略低此項。
比如
T(n) = n^3 + n^2 + 29,此時時間復雜度為 O(n^3)。
  1. 因為函數的階數對函數的增長速度的影響是最顯著的,所以我們忽略與最高階相乘的常數。
比如
T(n) = 3n^3,此時時間復雜度為 O(n^3)。

綜合起來:如果一個算法的執行次數是 T(n),那么只保留最高次項,同時忽略最高項的系數后得到函數 f(n),此時算法的時間復雜度就是 O(f(n))。為了方便描述,下文稱此為 大O推導法。

 

2-如何計算運算次數函數

由此可見,由執行次數 T(n) 得到時間復雜度並不困難,很多時候困難的是從算法通過分析和數學運算得到 T(n)。對此,提供下列四個便利的法則,這些法則都是可以簡單推導出來的,總結出來以便提高效率。

  1. 對於一個循環,假設循環體的時間復雜度為 O(n),循環次數為 m,則這個
    循環的時間復雜度為 O(n×m)。
void aFunc(int n) { for(int i = 0; i < n; i++) { // 循環次數為 n printf("Hello, World!\n"); // 循環體時間復雜度為 O(1) } } 

此時時間復雜度為 O(n × 1),即 O(n)。

  1. 對於多個循環,假設循環體的時間復雜度為 O(n),各個循環的循環次數分別是a, b, c...,則這個循環的時間復雜度為 O(n×a×b×c...)。分析的時候應該由里向外分析這些循環。注意:該方法只適用於,內外兩層的循環互不干擾的情況下!!!如果執行變量n在內/外循環中有關聯變化,則不適用,需單獨討論。詳見 3-練習題的 第一題。
void aFunc(int n) { for(int i = 0; i < n; i++) { // 循環次數為 n for(int j = 0; j < n; j++) { // 循環次數為 n printf("Hello, World!\n"); // 循環體時間復雜度為 O(1) } } } 

此時時間復雜度為 O(n × n × 1),即 O(n^2)。

  1. 對於順序執行的語句或者算法,總的時間復雜度等於其中最大的時間復雜度。
void aFunc(int n) { // 第一部分時間復雜度為 O(n^2) for(int i = 0; i < n; i++) { for(int j = 0; j < n; j++) { printf("Hello, World!\n"); } } // 第二部分時間復雜度為 O(n) for(int j = 0; j < n; j++) { printf("Hello, World!\n"); } } 

此時時間復雜度為 max(O(n^2), O(n)),即 O(n^2)。

  1. 對於條件判斷語句,總的時間復雜度等於其中 時間復雜度最大的路徑 的時間復雜度。
void aFunc(int n) { if (n >= 0) { // 第一條路徑時間復雜度為 O(n^2) for(int i = 0; i < n; i++) { for(int j = 0; j < n; j++) { printf("輸入數據大於等於零\n"); } } } else { // 第二條路徑時間復雜度為 O(n) for(int j = 0; j < n; j++) { printf("輸入數據小於零\n"); } } } 

此時時間復雜度為 max(O(n^2), O(n)),即 O(n^2)。

時間復雜度分析的基本策略是:從內向外分析,從最深層開始分析。如果遇到函數調用,要深入函數進行分析。

3-練習題

最后,我們來練習一下

一. 基礎題

求該方法的時間復雜度

void aFunc(int n) {
    for (int i = 0; i < n; i++) { for (int j = i; j < n; j++) { printf("Hello World\n"); } } } 

參考答案:
當 i = 0 時,內循環執行 n 次運算,當 i = 1 時,內循環執行 n - 1 次運算……當 i = n - 1 時,內循環執行 1 次運算。
所以,執行次數 T(n) = n + (n - 1) + (n - 2)……+ 1 = n(n + 1) / 2 = n^2 / 2 + n / 2。
根據上文說的 大O推導法 可以知道,此時時間復雜度為 O(n^2)。

注意:為何該題目不適合2.2(內外層循環次數相乘)的方法?因為該題中,內外兩層的循環因子有關聯!!!

內層的循環因子,會根據外層的因子變化。。。因此需要單獨分析,不能簡單相乘。

 

二. 進階題


求該方法的時間復雜度

void aFunc(int n) {
    for (int i = 2; i < n; i++) { i *= 2; printf("%i\n", i); } } 

參考答案:
假設循環次數為 t,則循環條件滿足 2^t < n。
可以得出,執行次數t = log(2)(n),即 T(n) = log(2)(n),可見時間復雜度為 O(log(2)(n)),即 O(log n)。

三. 再次進階


求該方法的時間復雜度

long aFunc(int n) {
    if (n <= 1) { return 1; } else { return aFunc(n - 1) + aFunc(n - 2); } } 

參考答案:

(我的答案)如下圖:總共這顆執行樹會有多少層呢?因為每層最右邊的那個,是觸摸最遠的那個。也就是說,每一層都比上一層最右側減去2,能減去的次數,其實就是(n-1)/ 2,約等於n/2。能減去的次數,就是執行樹的層數。

因此,最后的執行總次數是:2(0)+2(1)+2(2)+...+2(n/2)。

時間復雜度只考慮最大的指數O(2(n/2)),並忽略指數的項屬,答案就是O(2(n))

 


(原始答案)顯然運行次數,T(0) = T(1) = 1,同時 T(n) = T(n - 1) + T(n - 2) + 1,這里的 1 是其中的加法算一次執行。
顯然 T(n) = T(n - 1) + T(n - 2) 是一個斐波那契數列,通過歸納證明法可以證明,當 n >= 1 時 T(n) < (5/3)^n,同時當 n > 4 時 T(n) >= (3/2)^n。
所以該方法的時間復雜度可以表示為 O((5/3)^n),簡化后為 O(2^n)。
可見這個方法所需的運行時間是以指數的速度增長的。如果大家感興趣,可以試下分別用 1,10,100 的輸入大小來測試下算法的運行時間,相信大家會感受到時間復雜度的無窮魅力。

 

四、遞歸的時間復雜度計算

O(n) = 2^n

這里的2值得是每一層的計算量,n代表遞歸了多少層

 

 



作者:raymondCaptain
鏈接:https://www.jianshu.com/p/f4cca5ce055a
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權並注明出處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM