在上一篇文章中對時間複雜度及其舉例進行了簡單描述,本篇文章將分析算法的時間複雜度和相關方法。java
一、事前分析估算的方法程序員
因過後統計方法更多的依賴於計算機的硬件、軟件等環境因素,有時容易掩蓋算法自己的優劣。所以人們經常採用事前分析估算的方法。本文對過後統計方法不作描述。在編寫程序前,依據統計方法對算法進行估算。一個用高級語言編寫的程序在計算機上運行時所消耗的時間取決於下列因素:算法
(1). 算法採用的策略、方法;(2). 編譯產生的代碼質量;(3). 問題的輸入規模;(4). 機器執行指令的速度。函數
一個算法是由控制結構(順序、分支和循環3種)和原操做(指固有數據類型的操做)構成的,則算法時間取決於二者的綜合效果。爲了便於比較同一個問題的不一樣算法,一般的作法是,從算法中選取一種對於所研究的問題(或算法類型)來講是基本操做的原操做,以該基本操做的重複執行的次數做爲算法的時間量度。性能
二、時間複雜度測試
(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)) 爲算法的漸進時間複雜度,簡稱時間複雜度。spa
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的不斷增大,上述時間複雜度不短增大,算法的執行效率也越低。數學
常見的算法時間複雜度由小到大依次爲:Ο(1)<Ο(log2n)<Ο(n)<Ο(nlog2n)<Ο(n2)<Ο(n3)<…<Ο(2n)<Ο(n!)for循環
三、求解算法時間複雜度的具體步驟:編譯
找出算法中的基本語句;
算法中執行次數最多的那條語句就是基本語句,一般是最內層循環的循環體。
⑵ 計算基本語句的執行次數的數量級;
只需計算基本語句執行次數的數量級,這就意味着只要保證基本語句執行次數的函數中的最高次冪正確便可,能夠忽略全部低次冪和最高次冪的係數。這樣可以簡化算法分析,而且使注意力集中在最重要的一點上:增加率。
⑶ 用大Ο記號表示算法的時間性能。
將基本語句執行次數的數量級放入大Ο記號中。
若是算法中包含嵌套的循環,則基本語句一般是最內層的循環體,若是算法中包含並列的循環,則將並列循環的時間複雜度相加。例如:
for (i=1; i<=n; i++)
x++;
for (i=1; i<=n; i++)
for (j=1; j<=n; j++)
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問題。
四、簡單的程序分析法則:
(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是一個正常數
五、常見的時間複雜度分析說明:
(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的內容
解:由於Θ(2n2+n+1)=n2(Θ即:去低階項,去掉常數項,去掉高階項的常參獲得),因此T(n)= =O(n2);
2.2.
解: 語句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).
通常狀況下,對步進循環語句只需考慮循環體中語句的執行次數,忽略該語句中步長加一、終值判別、控制轉移等成分,當有若干個循環語句時,算法的時間複雜度是由嵌套層數最多的循環語句中最內層語句的頻度f(n)決定的。
(3)、O(n)
解: 語句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)
解: 語句1的頻度是1,
設語句2的頻度是f(n), 則:2^f(n)<=n;f(n)<=log2n
取最大值f(n)=log2n,
T(n)=O(log2n )
(5)、O(n3)
解:當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).
六、經常使用算法時間複雜度和空間複雜度
一個經驗規則:其中c是一個常量,若是一個算法的複雜度爲c 、 log2n 、n 、 n*log2n ,那麼這個算法時間效率比較高 ,若是是2n ,3n ,n!,那麼稍微大一些的n就會令這個算法不能動了,居於中間的幾個則差強人意。
算法時間複雜度分析是一個很重要的問題,任何一個程序員都應該熟練掌握其概念和基本方法,並且要善於從數學層面上探尋其本質,才能準確理解其內涵。