如何計算時間復雜度


一、概念
時間復雜度是總運算次數表達式中受n的變化影響最大的那一項(不含系數)
比如:一般總運算次數表達式類似於這樣:
a*2^n+b*n^3+c*n^2+d*n*lg(n)+e*n+f
a ! =0時,時間復雜度就是O(2^n);
a=0,b<>0 =>O(n^3);
a,b=0,c<>0 =>O(n^2)依此類推
eg:
(1)   for(i=1;i<=n;i++)   //循環了n*n次,當然是O(n^2)
            for(j=1;j<=n;j++)
                 s++;
(2)   for(i=1;i<=n;i++)//循環了(n+n-1+n-2+...+1)≈(n^2)/2,因為時間復雜度是不考慮系數的,所以也是O(n^2)
            for(j=i;j<=n;j++)
                 s++;
(3)   for(i=1;i<=n;i++)//循環了(1+2+3+...+n)≈(n^2)/2,當然也是O(n^2)
            for(j=1;j<=i;j++)
                 s++;
(4)   i=1;k=0;
      while(i<=n-1){
           k+=10*i;
i++; }
//循環了
n-1≈n次,所以是O(n)
(5) for(i=1;i<=n;i++)
             for(j=1;j<=i;j++)
                 for(k=1;k<=j;k++)
                       x=x+1;
//
循環了(1^2+2^2+3^2+...+n^2)=n(n+1)(2n+1)/6(這個公式要記住哦)≈(n^3)/3,不考慮系數,自然是O(n^3)
另外,在時間復雜度中,log(2,n)(以2為底)與lg(n)(以10為底)是等價的,因為對數換底公式:
 
        
log(a,b)=log(c,b)/log(c,a)
所以,log(2,n)=log(2,10)*lg(n),忽略掉系數,二者當然是等價的
二、計算方法
1.一個算法執行所耗費的時間,從理論上是不能算出來的,必須上機運行測試才能知道。但我們不可能也沒有必要對每個算法都上機測試,只需知道哪個算法花費的時間多,哪個算法花費的時間少就可以了。並且一個算法花費的時間與算法中語句的執行次數成正比例,哪個算法中語句執行次數多,它花費時間就多。
一個算法中的語句執行次數稱為語句頻度或時間頻度。記為T(n)。
2.一般情況下,算法的基本操作重復執行的次數是模塊n的某一個函數f(n),因此,算法的時間復雜度記做:T(n)=O(f(n))。隨着模塊n的增大,算法執行的時間的增長率和f(n)的增長率成正比,所以f(n)越小,算法的時間復雜度越低,算法的效率越高。
在計算時間復雜度的時候,先找出算法的基本操作,然后根據相應的各語句確定它的執行次數,再找出T(n)的同數量級(它的同數量級有以下:1,Log2n ,n ,nLog2n ,n的平方,n的三次方,2的n次方,n!),找出后,f(n)=該數量級,若T(n)/f(n)求極限可得到一常數c,則時間復雜度T(n)=O(f(n))。
3.常見的時間復雜度
按數量級遞增排列,常見的時間復雜度有:
常數階O(1),  對數階O(log2n),  線性階O(n),  線性對數階O(nlog2n),  平方階O(n^2), 立方階O(n^3),..., k次方階O(n^k), 指數階O(2^n) 。
其中,
1.O(n),O(n^2), 立方階O(n^3),..., k次方階O(n^k) 為多項式階時間復雜度,分別稱為一階時間復雜度,二階時間復雜度。。。。
2.O(2^n),指數階時間復雜度,該種不實用
3.對數階O(log2n),   線性對數階O(nlog2n),除了常數階以外,該種效率最高
例:算法:
  for(i=1;i<=n;++i)
  {
     for(j=1;j<=n;++j)
     {
         c[ i ][ j ]=0; //該步驟屬於基本操作 執行次數:n^2
          for(k=1;k<=n;++k)
               c[ i ][ j ]+=a[ i ][ k ]*b[ k ][ j ]; //該步驟屬於基本操作 執行次數:n^3
     }
  }
  則有 T(n)= n^2+n^3,根據上面括號里的同數量級,我們可以確定 n^3為T(n)的同數量級
  則有f(n)= n^3,然后根據T(n)/f(n)求極限可得到常數c
  則該算法的 時間復雜度:T(n)=O(n^3)
四、
定義:如果一個問題的規模是n,解這一問題的某一算法所需要的時間為T(n),它是n的某一函數 T(n)稱為這一算法的“時間復雜性”。

當輸入量n逐漸加大時,時間復雜性的極限情形稱為算法的“漸近時間復雜性”。

我們常用大O表示法表示時間復雜性,注意它是某一個算法的時間復雜性。大O表示只是說有上界,由定義如果f(n)=O(n),那顯然成立f(n)=O(n^2),它給你一個上界,但並不是上確界,但人們在表示的時候一般都習慣表示前者。

此外,一個問題本身也有它的復雜性,如果某個算法的復雜性到達了這個問題復雜性的下界,那就稱這樣的算法是最佳算法。

“大O記法”:在這種描述中使用的基本參數是 n,即問題實例的規模,把復雜性或運行時間表達為n的函數。這里的“O”表示量級 (order),比如說“二分檢索是 O(logn)的”,也就是說它需要“通過logn量級的步驟去檢索一個規模為n的數組”記法 O ( f(n) )表示當 n增大時,運行時間至多將以正比於 f(n)的速度增長。

這種漸進估計對算法的理論分析和大致比較是非常有價值的,但在實踐中細節也可能造成差異。例如,一個低附加代價的O(n2)算法在n較小的情況下可能比一個高附加代價的 O(nlogn)算法運行得更快。當然,隨着n足夠大以后,具有較慢上升函數的算法必然工作得更快。

O(1)

Temp=i;i=j;j=temp;                    

以上三條單個語句的頻度均為1,該程序段的執行時間是一個與問題規模n無關的常數。算法的時間復雜度為常數階,記作T(n)=O(1)。如果算法的執行時間不隨着問題規模n的增加而增長,即使算法中有上千條語句,其執行時間也不過是一個較大的常數。此類算法的時間復雜度是O(1)。

O(n^2)

2.1. 交換i和j的內容
     sum=0;                 (一次)
     for(i=1;i<=n;i++)       (n次 )
        for(j=1;j<=n;j++) (n^2次 )
         sum++;       (n^2次 )
解:T(n)=2n^2+n+1 =O(n^2)

2.2.   
    for (i=1;i<n;i++)
    {
        y=y+1;         ①   
        for (j=0;j<=(2*n);j++)    
           x++;        ②      
    }         
解: 語句1的頻度是n-1
          語句2的頻度是(n-1)*(2n+1)=2n^2-n-1
          f(n)=2n^2-n-1+(n-1)=2n^2-2
          該程序的時間復雜度T(n)=O(n^2).         

O(n)      
                                                      
2.3.
    a=0;
    b=1;                      ①
    for (i=1;i<=n;i++) ②
    {  
       s=a+b;    ③
       b=a;     ④  
       a=s;     ⑤
    }
解:語句1的頻度:2,        
           語句2的頻度: n,        
          語句3的頻度: n-1,        
          語句4的頻度:n-1,    
          語句5的頻度:n-1,                                  
          T(n)=2+n+3(n-1)=4n-1=O(n).
                                                                                                 
O(log2n )

2.4.
     i=1;       ①
    while (i<=n)
       i=i*2; ②
解: 語句1的頻度是1,  
          設語句2的頻度是f(n),   則:2^f(n)<=n;f(n)<=log2n    
          取最大值f(n)= log2n,
          T(n)=O(log2n )

O(n^3)

2.5.
    for(i=0;i<n;i++)
    {  
       for(j=0;j<i;j++)  
       {
          for(k=0;k<j;k++)
             x=x+2;  
       }
    }
解:當i=m, j=k的時候,內層循環的次數為k當i=m時, j 可以取 0,1,...,m-1 , 所以這里最內循環共進行了0+1+...+m-1=(m-1)m/2次所以,i從0取到n, 則循環共進行了: 0+(1-1)*1/2+...+(n-1)n/2=n(n+1)(n-1)/6所以時間復雜度為O(n^3).
                                  

我們還應該區分算法的最壞情況的行為和期望行為。如快速排序的最 壞情況運行時間是 O(n^2),但期望時間是 O(nlogn)。通過每次都仔細 地選擇基准值,我們有可能把平方情況 (即O(n^2)情況)的概率減小到幾乎等於 0。在實際中,精心實現的快速排序一般都能以 (O(nlogn)時間運行。
下面是一些常用的記法:


訪問數組中的元素是常數時間操作,或說O(1)操作。一個算法如 果能在每個步驟去掉一半數據元素,如二分檢索,通常它就取 O(logn)時間。用strcmp比較兩個具有n個字符的串需要O(n)時間。常規的矩陣乘算法是O(n^3),因為算出每個元素都需要將n對 元素相乘並加到一起,所有元素的個數是n^2。
指數時間算法通常來源於需要求出所有可能結果。例如,n個元 素的集合共有2n個子集,所以要求出所有子集的算法將是O(2n)的。指數算法一般說來是太復雜了,除非n的值非常小,因為,在 這個問題中增加一個元素就導致運行時間加倍。不幸的是,確實有許多問題 (如著名的“巡回售貨員問題” ),到目前為止找到的算法都是指數的。如果我們真的遇到這種情況,通常應該用尋找近似最佳結果的算法替代之。
 
https://blog.csdn.net/firefly_2002/article/details/8008987
 
https://blog.csdn.net/qq_15237565/article/details/77885311
 

數據結構中的時間復雜度的計算

算法的時間復雜度定義為:

時間復雜度或稱時間復雜性,又稱計算復雜度,她說是算法有效的度量之一,時間復雜度是一個算法運行時間的相對度量,一個算法的運行時間長短,它大致等於執行一種簡單操作所(賦值、比較、計算、轉向、返回、輸入和輸出)需要的時間與算法中進行簡單操作次數的乘積。

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

⑴   找出算法中的基本語句;
  一般算法中執行次數最多的那條語句就是基本語句,通常是最內層循環的循環體。

⑵  計算基本語句的執行次數的數量級;
  只需計算基本語句執行次數的數量級,這就意味着只要保證基本語句執行次數的函數中的最高次冪正確即可,可以忽略所有低次冪和最高次冪的系數。這樣能夠簡化算法分析,並且使注意力集中在最重要的一點上:增長率。

⑶  用大Ο記號表示算法的時間性能。
  當n趨近於無窮大時,如果lim(T(n)/f(n))的值為不等於0的常數,則稱f(n)是T(n)的同數量級函數。記作T(n)=O(f(n))。

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

舉個例子:

 

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

 

照上面推導“大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。
常規代碼:
  1. #include "stdio.h"
  2. int main()
  3. {
  4. int i, sum = 0, n = 100; /* 執行1次 */
  5. for( i = 1; i <= n; i++) /* 執行 n+1 次 */
  6. {
  7. sum = sum + i; /* 執行n次 */
  8. //printf("%d \n", sum);
  9. }
  10. printf("%d", sum); /* 執行1次 */
  11. }
從代碼附加的注釋可以看到所有代碼都執行了多少次。那么這寫代碼語句執行次數的總和就可以理解為是該算法計算出結果所需要的時間。該算法所用的時間(算法語句執行的總次數)為:  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)。
 
高斯算法:
  1. #include "stdio.h"
  2. int main()
  3. {
  4. int sum = 0, n = 100; /* 執行1次 */
  5. sum = ( 1 + n) * n/2; /* 執行1次 */
  1. printf("%d", sum); /* 執行1次 */
  2. }
 
               

這個算法的時間復雜度: O(3),但一般記作 O(1)。
從感官上我們就不難看出,從算法的效率上看,O(1) < O(n) 的,所以高斯的算法更快,更優秀。

 
二、求兩個  n 階方陣 C=A*B 的乘積其算法如下 :
  1. //右邊列為語句執行的頻度
  2. void MatrixMultiply(int A[n][n],int B [n][n],int C[n][n])
  3. {
  4. for(int i=0; i <n; i++) //n+1
  5. {
  6. for (j=0;j < n; j++) //n*(n+1)
  7. {
  8. C[i][j]= 0; //n^2
  9. for (k=0; k<n; k++) //n^2*(n+1)
  10. {
  11. C[i][j]=C[i][j]+A[i][k]*B[k][j]; //n^3
  12.  
  13. }
  14.  
  15. }
  16. }
  17. }

則該算法所有語句的頻度之和為:
T(n) = 2n^3+3n^2+2n+1;  利用大O表示法,該算法的時間復雜度為O(n^3)。

 

三、分析下列時間復雜度

 

  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)

四、分析下列時間復雜度

 

  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)為:

 

 

五、分析下列時間復雜度

  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(遞歸算法)時間復雜度分析


  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時候),這樣把一個大問題划分為多個子問題的求解。

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


免責聲明!

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



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