天天看點

時間複雜度入門了解

前言

  當你編寫完一個程式的時候,怎樣對它進行算法最優的判斷呢?效率又是怎樣展現的呢?效率=總執行次數/總時間,一般來說,程式越龐大,其執行效率越低。是以,對于子產品化程式,優化其算法的時間複雜度是非常重要的。

   

定義

  我們把一個算法中的語句執行次數定義為頻度,算法的運作時間刻畫為一個函數,定義為 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)

常見算法的時間複雜度 

時間複雜度入門了解