[C++]時間複雜度&空間複雜度
時間複雜度&空間複雜度
時間複雜度是演算法分析中常用的方法。他給出了數學方法來分析演算法的執行效率。本文討論如何計算時間複雜度,並給出相應的例項。而空間複雜度則是一個程式所消耗的記憶體成本。這兩個直接決定一個程式的執行效率以及一個演算法是否高效。
時間複雜度
定義
時間頻度
時間頻度 一個演算法執行所耗費的時間,從理論上是不能算出來的,必須上機執行測試才能知道。但我們不可能也沒有必要對每個演算法都上機測試,只需知道哪個演算法花費的時間多,哪個演算法花費的時間少就可以了。並且一個演算法花費的時間與演算法中語句的執行次數成正比例,哪個演算法中語句執行次數多,它花費時間就多。一個演算法中的語句執行次數稱為語句頻度或時間頻度。記為T(n)。
時間複雜度
如果一個問題的規模是n,解這一問題的某一演算法所需要的時間為T(n),它是n的某一函式 T(n)稱為這一演算法的“時間複雜性”。
當輸入量n逐漸加大時,時間複雜性的極限情形稱為演算法的“漸近時間複雜性”。
時間頻度不同,但時間複雜度可能相同。如: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記法
我們常用大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足夠大以後,具有較慢上升函式的演算法必然工作得更快。
最壞時間和平均時間
最壞情況下的時間複雜度稱最壞時間複雜度。一般不特別說明,討論的時間複雜度均是最壞情況下的時間複雜度。 這樣做的原因是:最壞情況下的時間複雜度是演算法在任何輸入例項上執行時間的上界,這就保證了演算法的執行時間不會比任何更長。
在最壞情況下的時間複雜度為T(n)=0(n),它表示對於任何輸入例項,該演算法的執行時間不可能大於0(n)。 平均時間複雜度是指所有可能的輸入例項均以等概率出現的情況下,演算法的期望執行時間。
計算方法
求解演算法的時間複雜度的具體步驟是:
⑴ 找出演算法中的基本語句;
演算法中執行次數最多的那條語句就是基本語句,通常是最內層迴圈的迴圈體。
⑵ 計算基本語句的執行次數的數量級;
只需計算基本語句執行次數的數量級,這就意味著只要保證基本語句執行次數的函式中的最高次冪正確即可,可以忽略所有低次冪和最高次冪的係數。這樣能夠簡化演算法分析,並且使注意力集中在最重要的一點上:增長率。
⑶ 用大Ο記號表示演算法的時間效能。
將基本語句執行次數的數量級放入大Ο記號中。
具體例項
O(1)
常數階,即時間複雜度不隨著規模n的增大而增大。如果演算法的執行時間不隨著問題規模n的增加而增長,即使演算法中有上千條語句,其執行時間也不過是一個較大的常數
int x=91;
int y=100;
while(y>0) {
if(x>100) {
x=x-10;
y--;
} else {
x++;
}
}
T(n)=O(1),
這個程式看起來有點嚇人,總共迴圈運行了1000次,但是我們看到n沒有?沒。這段程式的執行是和n無關的,
就算它再迴圈一萬年,我們也不管他,只是一個常數階的函式。
O(n)
一次階時間複雜度。耗費的時間與規模n成一階線性關係。
int n;
cin >> n;
while (n--) {
cout << n << endl;
}
O(n^3)
三階線性關係。(n階線性以此類推即可。)就好像矩陣相乘就永遠是O(n^3 )
int total = 0;
int n;
cin >> n;
for (int i = 0; i != n; i++) {
for (int j = 0; j != i; j++) {
for (int k = 0; j != j; k++) {
total++;
}
}
}
內迴圈的執行次數雖然與問題規模n沒有直接關係,但是卻與外層迴圈的變數取值有關,而最外層迴圈的次數直接與n有關,因此可以從內層迴圈向外層分析語句的執行次數:則該程式段的時間複雜度為T(n)=O(n^3 /6+低次項)=O(n^3)
常見的演算法時間複雜度由小到大依次為:
Ο(1)<Ο(log2(n)<Ο(n)<Ο(nlog2(n))<Ο(n^2) <Ο(n^3) <…<Ο(2^n) <Ο(n!)
分析法則
在計算演算法時間複雜度時有以下幾個簡單的程式分析法則:
1.對於一些簡單的輸入輸出語句或賦值語句,近似認為需要O(1)時間
2.對於順序結構,需要依次執行一系列語句所用的時間可採用大O下”求和法則”
3.求和法則:是指若演算法的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)固定部分。這部分空間的大小與輸入/輸出的資料的個數多少、數值無關。主要包括指令空間(即程式碼空間)、資料空間(常量、簡單變數)等所佔的空間。這部分屬於靜態空間。
(2)可變空間,這部分空間的主要包括動態分配的空間,以及遞迴棧所需的空間等。這部分的空間大小與演算法有關。
一個演算法所需的儲存空間用f(n)表示。S(n)=O(f(n)) 其中n為問題的規模,S(n)表示空間複雜度。