時間復雜度入門理解


前言


 

  當你編寫完一個程序的時候,怎樣對它進行算法最優的判斷呢?效率又是怎樣體現的呢?效率=總執行次數/總時間,一般來說,程序越龐大,其執行效率越低。因此,對於模塊化程序,優化其算法的時間復雜度是非常重要的。

   

定義


 

  我們把一個算法中的語句執行次數定義為頻度,算法的運行時間刻畫為一個函數,定義為 T(n) ,其中n稱為問題的規模,當n不斷變化時,T(n)也隨之變化。但我們想知道n與T(n)之間的大致規律,所以我們引入漸進符號 O 來刻畫算法的運行時間。

  現在我們編寫一個程序,把其中表示算法運行時間的函數記為 T(n)。對於一個給定的函數 g(n),有 O(g(n)) = {f(n) | 存在常量 c, n0, c>0, n0>0, 使得對所有 n ≥ n0, 有0  ≤ f(n)  ≤ c·g(n)},記作 f(n) = O(g(n)) 。如下圖。注意這個等號並不是左右相等,而是代表集合論中的 “∈”,表示 'is a ..' 的關系,如“ n = O(n2) ”。當我們說運行時間為 O(g(n)) 時,表示存在一個O(g(n)) 的函數 f(n),使得 n 不管輸入什么值時,T(n) 的上界都是 f(n)。所以 O(g(n)) 表示最壞情況運行時間。

  通常,我們稱 O(g(n)) 為時間復雜度。

  

      圖(a) f(n) = O(g(n))  

 

按增長量級遞增排列,常見的時間復雜度有:
常數階O(1),  對數階O(log2n),  線性階O(n),  線性對數階O(nlog2n),  平方階O(n^2), 立方階O(n^3),..., k次方階O(n^k), 指數階O(2^n) 。

    

 計算時間復雜度

 
粗略地計算的話就知道以下三步即可:
     
  1.去掉運行時間中的所有加法常數。
     2.只保留最高階項。
     3.如果最高階項存在且不是1,去掉與這個最高階相乘的常數得到時間復雜度

 

我們看一個例子
 for (int i = 0; i < n; i++) {

          for (int j = i; j < n; j++) {

               // do .....

          }

     }

  

當 i = 0 時 里面的for循環執行了n次,當i等待1時里面的for循環執行了n -  1次,當i 等於2里里面的fro執行了n - 2次........這樣,就可以將循環對應成等差數列,項數為最外層循環的次數,公差為1,首項是i = 0  or  i = n-1 時內層循環的執行次數,末項是i = n-1  or  i = 0 時的內層循環的執行次數所以執行的次數是

 

那么得到運行時間的函數    (c 是常數)

 

根據我們上邊的時間復雜度算法

 

     1.去掉運行時間中的所有加法常數: 沒有加法常數不用考慮

 

     2.只保留最高階項: 只保留 

 

     3. 去掉與這個最高階相乘的常數:  去掉 只剩下 

 

     最終這個算法的時間復雜度為
 

 下面拿幾道題練練手:

(1)
 for(i=1;i<=n;i++)   
            for(j=1;j<=n;j++)
                 s++;

//循環了n*n次,當然是O(n^2)
(2)
     for(i=1;i<=n;i++)
            for(j=i;j<=n;j++)
                 s++;
    
    //循環了(n+n-1+n-2+...+1)≈(n^2)/2,因為時間復雜度是不考慮系數的,所以也是O(n^2)
(3)
  for(i=1;i<=n;i++)
            for(j=1;j<=i;j++)
                 s++;

//循環了(1+2+3+...+n)≈(n^2)/2,當然也是O(n^2)
(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)=lgn/lg2,忽略掉系數,二者當然是等價的


在各種不同算法中,若算法中語句執行次數為一個常數,則時間復雜度為O(1),譬如簡單的求和,代碼如下,其頻度為3,O(1)

int a,b; //頻度為2 printf("%d",a+b); //頻度為1 return 0; 

   

實踐出真知,下面放一些更難的例題,幫助理解與計算時間復雜度

   

例一:下面是求50以內的奇數和的代碼,求時間復雜度
 
(例 1.1)
 
 
(例 1.2)
    注意:其中的 floor() 和 ceil() 分別是向下取整和向上取整
 
 
例二:
 
while(n!=0)
{
    n/=10;  
}

  

時間復雜度是O(lgn)
    解析:設規模為n,運行時間為T(n)。
            若n有五位數,則語句執行五次。
            設變量N==位數,則有當n有N位數時,語句執行N次。得N^10=n.
            則lgn=N。 此時運行時間與問題規模的關系為 T(n) = c*N = c*lgn (c是常數) 
            所以時間復雜度為 O(lgn)
 
 
 
例三:
 
while(n!=0)
{
    n=n/2;
}

  

    時間復雜度O(lgn)
    解析:
                設n=2^m,則循環執行了m+1次,m=1+log2n=1 + lg(n)/lg(2),因此頻度為 1+lg(n)/lg(2),運行時間 T(n) = 1 + lg(n) / lg(2)
                時間復雜度為 O(lgn)
 
例四:
void func(int n)
{ 
    int i=0,s=0;
    while(s<n)
    { 
        i++;
        s=s+i;
    }
}    

  時間復雜度O(n^(1/2))

  解析:    

  測試樣例: n = 3,5,9,...n^2 ,可得頻度 N = 2,3,4,...n^(1/2) (近似計算)

  則運行時間 T(n) = c*(n^(1/2)) + c2 (c, c2為常數),可得時間復雜度 O(n^(1/2))
 
  
例五:  
  x=91; y=100;
  while(y>0)
  {
           if(x>100)
      {
                x=x-10;
                y--;
            }
      else x++;
  }                    
  這題易錯當為線性階O(n),我自己就犯了循環就是常數階的錯誤,其實這是常數階O(1)
 
 
常見算法的時間復雜度
 

 


免責聲明!

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



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