算法的時間複雜度和空間複雜度-總結

算法的時間複雜度和空間複雜度-總結

        通常,對於一個給定的算法,我們要做 兩項分析。第一是從數學上證明算法的正確性,這一步主要用到形式化證明的方法及相關推理模式,如循環不變式、數學歸納法等。而在證明算法是正確的基礎上,第二部就是分析算法的時間複雜度。算法的時間複雜度反映了程序執行時間隨輸入規模增長而增長的量級,在很大程度上能很好反映出算法的優劣與否。因此,作爲程序員,掌握基本的算法時間複雜度分析方法是很有必要的。
       算法執行時間需通過依據該算法編制的程序在計算機上運行時所消耗的時間來度量。而度量一個程序的執行時間通常有兩種方法。

一、事後統計的方法

        這種方法可行,但不是一個好的方法。該方法有兩個缺陷:一是要想對設計的算法的運行性能進行評測,必須先依據算法編制相應的程序並實際運行;二是所得時間的統計量依賴於計算機的硬件、軟件等環境因素,有時容易掩蓋算法本身的優勢。

二、事前分析估算的方法

        因事後統計方法更多的依賴於計算機的硬件、軟件等環境因素,有時容易掩蓋算法本身的優劣。因此人們常常採用事前分析估算的方法。

在編寫程序前,依據統計方法對算法進行估算。一個用高級語言編寫的程序在計算機上運行時所消耗的時間取決於下列因素:

      (1). 算法採用的策略、方法;(2). 編譯產生的代碼質量;(3). 問題的輸入規模;(4).  機器執行指令的速度。

     一個算法是由控制結構(順序、分支和循環3種)和原操作(指固有數據類型的操作)構成的,則算法時間取決於兩者的綜合效果。爲了便於比較同一個問題的不同算法,通常的做法是,從算法中選取一種對於所研究的問題(或算法類型)來說是基本操作的原操作,以該基本操作的重複執行的次數作爲算法的時間量度。

1、時間複雜度 
(1)時間頻度
 一個算法執行所耗費的時間,從理論上是不能算出來的,必須上機運行測試才能知道。但我們不可能也沒有必要對每個算法都上機測試,只需知道哪個算法花費的時間多,哪個算法花費的時間少就可以了。並且一個算法花費的時間與算法中語句的執行次數成正比例,哪個算法中語句執行次數多,它花費時間就多。一個算法中的語句執行次數稱爲語句頻度或時間頻度。記爲T(n)。
(2)時間複雜度 在剛纔提到的時間頻度中,n稱爲問題的規模,當n不斷變化時,時間頻度T(n)也會不斷變化。但有時我們想知道它變化時呈現什麼規律。爲此,我們引入時間複雜度概念。 一般情況下,算法中基本操作重複執行的次數是問題規模n的某個函數,用T(n)表示,若有某個輔助函數f(n),使得當n趨近於無窮大時,T(n)/f(n)的極限值爲不等於零的常數,則稱f(n)是T(n)的同數量級函數。記作T(n)=O(f(n)),O(f(n)) 爲算法的漸進時間複雜度,簡稱時間複雜度。

       另外,上面公式中用到的 Landau符號其實是由德國數論學家保羅·巴赫曼(Paul Bachmann)在其1892年的著作《解析數論》首先引入,由另一位德國數論學家艾德蒙·朗道(Edmund Landau)推廣。Landau符號的作用在於用簡單的函數來描述複雜函數行爲,給出一個上或下(確)界。在計算算法複雜度時一般只用到大O符號,Landau符號體系中的小o符號、Θ符號等等比較不常用。這裏的O,最初是用大寫希臘字母,但現在都用大寫英語字母O;小o符號也是用小寫英語字母oΘ符號則維持大寫希臘字母Θ
        T (n) = Ο(f (n)) 表示存在一個常數C,使得在當n趨於正無窮時總有 T (n) ≤ C * f(n)。簡單來說,就是T(n)在n趨於正無窮時最大也就跟f(n)差不多大。也就是說當n趨於正無窮時T (n)的上界是C * f(n)。其雖然對f(n)沒有規定,但是一般都是取儘可能簡單的函數。例如,O(2n2+n +1) = O (3n2+n+3) = O (7n2 + n) = O ( n2 ) ,一般都只用O(n2)表示就可以了。注意到大O符號裏隱藏着一個常數C,所以f(n)裏一般不加係數。如果把T(n)當做一棵樹,那麼O(f(n))所表達的就是樹幹,只關心其中的主幹,其他的細枝末節全都拋棄不管。
        在各種不同算法中,若算法中語句執行次數爲一個常數,則時間複雜度爲O(1),另外,在時間頻度不相同時,時間複雜度有可能相同,如T(n)=n2+3n+4與T(n)=4n2+2n+1它們的頻度不同,但時間複雜度相同,都爲O(n2)。 按數量級遞增排列,常見的時間複雜度有:常數階O(1),對數階O(log2n),線性階O(n), 線性對數階O(nlog2n),平方階O(n2),立方階O(n3),..., k次方階O(nk),指數階O(2n)。隨着問題規模n的不斷增大,上述時間複雜度不斷增大,算法的執行效率越低。

   從圖中可見,我們應該儘可能選用多項式階O(nk)的算法,而不希望用指數階的算法。

      常見的算法時間複雜度由小到大依次爲:Ο(1)<Ο(log2n)<Ο(n)<Ο(nlog2n)<Ο(n2)<Ο(n3)<…<Ο(2n)<Ο(n!)

       一般情況下,對一個問題(或一類算法)只需選擇一種基本操作來討論算法的時間複雜度即可,有時也需要同時考慮幾種基本操作,甚至可以對不同的操作賦予不同的權值,以反映執行不同操作所需的相對時間,這種做法便於綜合比較解決同一問題的兩種完全不同的算法。

(3)求解算法的時間複雜度的具體步驟是:

  ⑴ 找出算法中的基本語句;

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

  ⑵ 計算基本語句的執行次數的數量級;

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

  ⑶ 用大Ο記號表示算法的時間性能。

  將基本語句執行次數的數量級放入大Ο記號中。

  如果算法中包含嵌套的循環,則基本語句通常是最內層的循環體,如果算法中包含並列的循環,則將並列循環的時間複雜度相加。例如:

[java] view plaincopy
  1. for (i=1; i<=n; i++)  
  2.        x++;  
  3. for (i=1; i<=n; i++)  
  4.      for (j=1; j<=n; j++)  
  5.           x++;  

  第一個for循環的時間複雜度爲Ο(n),第二個for循環的時間複雜度爲Ο(n2),則整個算法的時間複雜度爲Ο(n+n2)=Ο(n2)。

  Ο(1)表示基本語句的執行次數是一個常數,一般來說,只要算法中不存在循環語句,其時間複雜度就是Ο(1)。其中Ο(log2n)、Ο(n)、 Ο(nlog2n)、Ο(n2)和Ο(n3)稱爲多項式時間,而Ο(2n)和Ο(n!)稱爲指數時間。計算機科學家普遍認爲前者(即多項式時間複雜度的算法)是有效算法,把這類問題稱爲P(Polynomial,多項式)類問題,而把後者(即指數時間複雜度的算法)稱爲NP(Non-Deterministic Polynomial, 非確定多項式)問題

        一般來說多項式級的複雜度是可以接受的,很多問題都有多項式級的解——也就是說,這樣的問題,對於一個規模是n的輸入,在n^k的時間內得到結果,稱爲P問題。有些問題要複雜些,沒有多項式時間的解,但是可以在多項式時間裏驗證某個猜測是不是正確。比如問4294967297是不是質數?如果要直接入手的話,那麼要把小於4294967297的平方根的所有素數都拿出來,看看能不能整除。還好歐拉告訴我們,這個數等於641和6700417的乘積,不是素數,很好驗證的,順便麻煩轉告費馬他的猜想不成立。大數分解、Hamilton迴路之類的問題,都是可以多項式時間內驗證一個“解”是否正確,這類問題叫做NP問題。

(4)在計算算法時間複雜度時有以下幾個簡單的程序分析法則:

(1).對於一些簡單的輸入輸出語句或賦值語句,近似認爲需要O(1)時間

(2).對於順序結構,需要依次執行一系列語句所用的時間可採用大O下"求和法則"

求和法則:是指若算法的2個部分時間複雜度分別爲 T1(n)=O(f(n))和 T2(n)=O(g(n)),則 T1(n)+T2(n)=O(max(f(n), g(n)))

特別地,若T1(m)=O(f(m)), T2(n)=O(g(n)),則 T1(m)+T2(n)=O(f(m) + g(n))

(3).對於選擇結構,如if語句,它的主要時間耗費是在執行then字句或else字句所用的時間,需注意的是檢驗條件也需要O(1)時間

(4).對於循環結構,循環語句的運行時間主要體現在多次迭代中執行循環體以及檢驗循環條件的時間耗費,一般可用大O下"乘法法則"

乘法法則: 是指若算法的2個部分時間複雜度分別爲 T1(n)=O(f(n))和 T2(n)=O(g(n)),則 T1*T2=O(f(n)*g(n))

(5).對於複雜的算法,可以將它分成幾個容易估算的部分,然後利用求和法則和乘法法則技術整個算法的時間複雜度

另外還有以下2個運算法則:(1) 若g(n)=O(f(n)),則O(f(n))+ O(g(n))= O(f(n));(2) O(Cf(n)) = O(f(n)),其中C是一個正常數

 (5)下面分別對幾個常見的時間複雜度進行示例說明:

(1)、O(1)

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

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

(2)、O(n2)

2.1. 交換i和j的內容

[java] view plaincopy
  1. sum=0;                 (一次)  
  2. for(i=1;i<=n;i++)     (n+1次)  
  3.    for(j=1;j<=n;j++) (n2次)  
  4.     sum++;            (n2次)  

解:因爲Θ(2n2+n+1)=n2(Θ即:去低階項,去掉常數項,去掉高階項的常參得到),所以T(n)= =O(n2);

2.2.   

[java] view plaincopy
  1. for (i=1;i<n;i++)  
  2.  {   
  3.      y=y+1;         ①     
  4.      for (j=0;j<=(2*n);j++)      
  5.         x++;         ②        
  6.  }            

解: 語句1的頻度是n-1
          語句2的頻度是(n-1)*(2n+1)=2n2-n-1
          f(n)=2n2-n-1+(n-1)=2n2-2;

        又Θ(2n2-2)=n2
          該程序的時間複雜度T(n)=O(n2).  

  一般情況下,對步進循環語句只需考慮循環體中語句的執行次數,忽略該語句中步長加1、終值判別、控制轉移等成分當有若干個循環語句時,算法的時間複雜度是由嵌套層數最多的循環語句中最內層語句的頻度f(n)決定的。     

(3)、O(n)                                                              

[java] view plaincopy
  1. a=0;  
  2.   b=1;                      ①  
  3.   for (i=1;i<=n;i++) ②  
  4.   {    
  5.      s=a+b;    ③  
  6.      b=a;     ④    
  7.      a=s;     ⑤  
  8.   }  

解: 語句1的頻度:2,        
           語句2的頻度: n,        
          語句3的頻度: n-1,        
          語句4的頻度:n-1,    
          語句5的頻度:n-1,                                  
          T(n)=2+n+3(n-1)=4n-1=O(n).
(4)、O(log2n)

[java] view plaincopy
  1. i=1;     ①  
  2. hile (i<=n)  
  3.   i=i*2; ②  

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

(5)、O(n3) 

[java] view plaincopy
  1. for(i=0;i<n;i++)  
  2.    {    
  3.       for(j=0;j<i;j++)    
  4.       {  
  5.          for(k=0;k<j;k++)  
  6.             x=x+2;    
  7.       }  
  8.    }  

解:當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(n3).

(5)常用的算法的時間複雜度和空間複雜度

一個經驗規則:其中c是一個常量,如果一個算法的複雜度爲c 、 log2n 、n 、 n*log2n ,那麼這個算法時間效率比較高 ,如果是2n ,3n ,n!,那麼稍微大一些的n就會令這個算法不能動了,居於中間的幾個則差強人意。

       算法時間複雜度分析是一個很重要的問題,任何一個程序員都應該熟練掌握其概念和基本方法,而且要善於從數學層面上探尋其本質,才能準確理解其內涵。

2、算法的空間複雜度

        類似於時間複雜度的討論,一個算法的空間複雜度(Space Complexity)S(n)定義爲該算法所耗費的存儲空間,它也是問題規模n的函數。漸近空間複雜度也常常簡稱爲空間複雜度。
空間複雜度(Space Complexity)是對一個算法在運行過程中臨時佔用存儲空間大小的量度。一個算法在計算機存儲器上所佔用的存儲空間,包括存儲算法本身所佔用的存儲空間,算法的輸入輸出數據所佔用的存儲空間和算法在運行過程中臨時佔用的存儲空間這三個方面。算法的輸入輸出數據所佔用的存儲空間是由要解決的問題決定的,是通過參數表由調用函數傳遞而來的,它不隨本算法的不同而改變。存儲算法本身所佔用的存儲空間與算法書寫的長短成正比,要壓縮這方面的存儲空間,就必須編寫出較短的算法。算法在運行過程中臨時佔用的存儲空間隨算法的不同而異,有的算法只需要佔用少量的臨時工作單元,而且不隨問題規模的大小而改變,我們稱這種算法是“就地\"進行的,是節省存儲的算法,如這一節介紹過的幾個算法都是如此;有的算法需要佔用的臨時工作單元數與解決問題的規模n有關,它隨着n的增大而增大,當n較大時,將佔用較多的存儲單元,例如將在第九章介紹的快速排序和歸併排序算法就屬於這種情況。

當一個算法的空間複雜度爲一個常量,即不隨被處理數據量n的大小而改變時,可表示爲O(1);當一個算法的空間複雜度與以2爲底的n的對數成正比時,可表示爲0(10g2n);當一個算法的空I司複雜度與n成線性比例關係時,可表示爲0(n).若形參爲數組,則只需要爲它分配一個存儲由實參傳送來的一個地址指針的空間,即一個機器字長空間;若形參爲引用方式,則也只需要爲其分配存儲一個地址的空間,用它來存儲對應實參變量的地址,以便由系統自動引用實參變量。

參考1:http://www.cnblogs.com/songQQ/archive/2009/10/20/1587122.html

參考2 :http://www.cppblog.com/85940806/archive/2011/03/12/141672.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章