數據結構-算法-時間複雜度計算

算法的時間複雜度定義爲:

在進行算法分析時,語句總的執行次數T(n)是關於問題規模n的函數,進而分析T(n)隨n的變化情況並確定T(n)的數量級。算法的時間複雜度,也就是算法的時間量度,記作:T(n}=0(f(n))。它表示隨問題規模n的增大,算法執行時間的埔長率和 f(n)的埔長率相同,稱作算法的漸近時間複雜度,簡稱爲時間複雜度。其中f( n)是問題規橫n的某個函數。

根據定義,求解算法的時間複雜度的具體步驟是:

  ⑴ 找出算法中的基本語句;
  算法中執行次數最多的那條語句就是基本語句,通常是最內層循環的循環體。
  ⑵ 計算基本語句的執行次數的數量級;
  只需計算基本語句執行次數的數量級,這就意味着只要保證基本語句執行次數的函數中的最高次冪正確即可,可以忽略所有低次冪和最高次冪的係數。這樣能夠簡化算法分析,並且使注意力集中在最重要的一點上:增長率。
  ⑶ 用大Ο記號表示算法的時間性能。
  將基本語句執行次數的數量級放入大Ο記號中。


如何推導大o階呢?我們給出了下面 的推導方法:

1.用常數1取代運行時間中的所有加法常數。
2.在修改後的運行次數函數中,只保留最髙階項。
3.如果最高階項存在且不是1,則去除與這個項相乘的常數。

簡單的說,就是保留求出次數的最高次冪,並且把係數去掉。  如T(n)=2n^2+n+1 =O(n^2)

舉個例子。

[cpp] view plain copy
  1. #include "stdio.h"  
  2.   
  3. int main()  
  4. {  
  5.     int i, j, x = 0, sum = 0, n = 100;  /* 執行1次 */  
  6.     for( i = 1; i <= n; i++)    /* 執行n+1次 */  
  7.     {  
  8.         sum = sum + i;               /* 執行n次 */     
  9.         for( j = 1; j <= n; j++)    /* 執行n*(n+1)次 */  
  10.         {  
  11.             x++;                /* 執行n*n次 */  
  12.             sum = sum + x;      /* 執行n*n次 */  
  13.         }  
  14.     }  
  15.     printf("%d", sum);          /* 執行1次 */  
  16. }  


按照上面推導“大O階”的步驟,我們來看

第一步:“用常數 1 取代運行時間中的所有加法常數”,

則上面的算式變爲:執行總次數 =3n^2 + 3n + 1

(直接相加的話,應該是T(n) = 1 + n+1 + n + n*(n+1) + n*n + n*n + 1 = 3n^2 + 3n + 3。現在用常數 1 取代運行時間中的所有加法常數,就是把T(n) = 3n^2 + 3n + 3中的最後一個3改爲1. 就得到了 T(n) = 3n^2 + 3n + 1)


第二步:“在修改後的運行次數函數中,只保留最高階項”。

這裏的最高階是 n 的二次方,所以算式變爲:執行總次數 = 3n^2


第三步:“如果最高階項存在且不是 1 ,則去除與這個項相乘的常數”。

這裏 n 的二次方不是 1 所以要去除這個項的相乘常數,算式變爲:執行總次數 = n^2


因此最後我們得到上面那段代碼的算法時間複雜度表示爲: O( n^2 )


下面我把常見的算法時間複雜度以及他們在效率上的高低順序記錄在這裏,使大家對算法的效率有個直觀的認識。

O(1) 常數階 < O(logn) 對數階 < O(n) 線性階 < O(nlogn) < O(n^2) 平方階 < O(n^3) < { O(2^n) < O(n!) < O(n^n) }

最後三項用大括號把他們括起來是想要告訴大家,如果日後大家設計的算法推導出的“大O階”是大括號中的這幾位,那麼趁早放棄這個算法,在去研究新的算法出來吧。因爲大括號中的這幾位即便是在 n 的規模比較小的情況下仍然要耗費大量的時間,算法的時間複雜度大的離譜,基本上就是“不可用狀態”。



好了,原理就介紹到這裏了。下面通過幾個例子具體分析下時間複雜度計算過程。

一。計算 1 + 2 + 3 + 4 + ...... + 100。

常規算法,代碼如下:

[cpp] view plain copy
  1. #include "stdio.h"  
  2.   
  3. int main()  
  4. {  
  5.     int i, sum = 0, n = 100;    /* 執行1次 */  
  6.     for( i = 1; i <= n; i++) /* 執行 n+1 次 */  
  7.     {  
  8.         sum = sum + i;          /* 執行n次 */  
  9.         //printf("%d \n", sum);  
  10.     }  
  11.     printf("%d", sum);          /* 執行1次 */  
  12. }  

從代碼附加的註釋可以看到所有代碼都執行了多少次。那麼這寫代碼語句執行次數的總和就可以理解爲是該算法計算出結果所需要的時間。該算法所用的時間(算法語句執行的總次數)爲: 1 + ( n + 1 ) + n + 1 = 2n + 3

而當 n 不斷增大,比如我們這次所要計算的不是 1 + 2 + 3 + 4 + ...... + 100 = ? 而是 1 + 2 + 3 + 4 + ...... + n = ?其中 n 是一個十分大的數字,那麼由此可見,上述算法的執行總次數(所需時間)會隨着 n 的增大而增加,但是在 for 循環以外的語句並不受 n 的規模影響(永遠都只執行一次)。所以我們可以將上述算法的執行總次數簡單的記做: 2n 或者簡記 n

這樣我們就得到了我們設計的算法的時間複雜度,我們把它記作: O(n)


再來看看高斯的算法,代碼如下:

[cpp] view plain copy
  1. #include "stdio.h"  
  2.   
  3. int main()  
  4. {  
  5.     int sum = 0, n = 100;   /* 執行1次 */  
  6.     sum = (1 + n) * n/2;    /* 執行1次 */  
  7.   
  8.     printf("%d", sum);      /* 執行1次 */  
  9. }  

這個算法的時間複雜度: O(3),但一般記作 O(1)。

從感官上我們就不難看出,從算法的效率上看,O(1) < O(n) 的,所以高斯的算法更快,更優秀。

這也就難怪爲什麼每本算法書開篇都是拿高斯的這個例子來舉例了(至少我看的都是)...人家也確實有那個資本。



二。求兩個n階方陣C=A*B的乘積其算法如下:

[cpp] view plain copy
  1. //右邊列爲語句執行的頻度  
  2.   
  3.    void MatrixMultiply(int A[n][n],int B [n][n],int C[n][n])  
  4.   
  5.    {  
  6.   
  7. (1) for(int i=0; i <n; i++)                       //n+1  
  8.   
  9.       {  
  10.   
  11. (2)      for (j=0;j < n; j++)                       //n*(n+1)  
  12.   
  13.            {  
  14.   
  15. (3)           C[i][j]=0;                                  //n^2  
  16.   
  17. (4)           for (k=0; k<n; k++)                 //n^2*(n+1)  
  18.   
  19.                {  
  20.   
  21. (5)              C[i][j]=C[i][j]+A[i][k]*B[k][j]; //n^3  
  22.   
  23.               }  
  24.   
  25.           }  
  26.   
  27.       }  
  28.   
  29.   }  
則該算法所有語句的頻度之和爲:

T(n) = 2n^3+3n^2+2n+1;  利用大O表示法,該算法的時間複雜度爲O(n^3)。


三。分析下列時間複雜度

[cpp] view plain copy
  1. void test_(int n)  
  2. {  
  3.     i = 1, k = 100;  
  4.     while (i<n)  
  5.     {  
  6.         k = k + 1;  
  7.         i += 2;  
  8.     }  
  9. }  

設for循環語句執行次數爲T(n),則 i = 2T(n) + 1 <= n - 1,  即T(n) <= n/2 - 1 = O(n)


四。分析下列時間複雜度

[cpp] view plain copy
  1. void test_2(int b[], int n)  
  2. {  
  3.     int i, j, k;  
  4.     for (i=0; i<n-1; i++)  
  5.     {  
  6.         k = i;  
  7.         for (j=i+1; j<n; j++)  
  8.         {  
  9.             if (b[k] > b[j])  
  10.             {  
  11.                 k = j;  
  12.             }  
  13.         }  
  14.         x = b[i];  
  15.         b[i] = b[k];  
  16.         b[k] = x;  
  17.     }  
  18. }  

其中,算法的基本運算語句是

if (b[k] > b[j])

{

   k = j;

}

其執行次數T(n)爲:


五。分析下列時間複雜度

[cpp] view plain copy
  1. void test_3(int n)  
  2. {  
  3.     int i = 0, s = 0;  
  4.     while (s<n)  
  5.     {  
  6.         i++;  
  7.         s = s + i;  
  8.     }  
  9. }  


其中,算法的基本運算語句即while循環內部分,

設while循環語句執行次數爲T(n),則


六。Hanoi(遞歸算法)時間複雜度分析

[cpp] view plain copy
  1. void hanoi(int n, char a, char b, char c)  
  2. {  
  3.     if (n==1)  
  4.     {  
  5.         printf("move %d disk from %c to %c \n", n, a, c);  //執行一次  
  6.     }  
  7.     else  
  8.     {  
  9.         hanoi(n-1, a, c, b);    //遞歸n-1次  
  10.         printf("move %d disk from %c to %c \n", n, a, c);  //執行一次  
  11.         hanoi(n-1, b, a, c);    //遞歸n-1次  
  12.     }  
  13. }  

對於遞歸函數的分析,跟設計遞歸函數一樣,要先考慮基情況(比如hanoi中n==1時候),這樣把一個大問題劃分爲多個子問題的求解。

故此上述算法的時間複雜度的遞歸關係如下:







發佈了54 篇原創文章 · 獲贊 87 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章