算法時間復雜度和空間復雜度的計算
算法,即解決問題的方法。同一個問題,使用不同的算法,雖然得到的結果相同,但是耗費的時間和資源是不同的。
就比如要擰一個螺母,使用扳手還是鉗子是有區別的,雖然使用鉗子也能擰螺母,但是沒有扳手好用。
“條條大路通羅馬”,解決問題的算法有多種,這就需要判斷哪個算法“更好”。
算法VS程序
很多人誤以為程序就是算法,其實不然:算法是解決某個問題的想法、思路;而程序是在心中有算法的前提下編寫出來的可以運行的代碼。
例如,要解決依次輸出一維數組中的數據元素的值的問題,首先想到的是使用循環結構( for 或者 while ),在有這個算法的基礎上,開始編寫程序。
所以,算法相當於是程序的雛形。當解決問題時,首先心中要有解決問題的算法,圍繞算法編寫出程序代碼。
有算法一定能解決問題嗎?
對於一個問題,想出解決的算法,不一定就能解決這個問題。
例如擰螺母,扳手相對於鉗子來說更好使(選擇算法的過程),但是在擰的過程(編寫程序的過程)中發現螺母生銹擰不動,這時就需要另想辦法。
為了避免這種情況的發生,要充分全面地思考問題,盡可能地考慮到所有地可能情況,慎重選擇算法(需要在實踐中不斷地積累經驗)。
“好”算法的標準
對於一個問題的算法來說,之所以稱之為算法,首先它必須能夠解決這個問題(稱為準確性)。其次,通過這個算法編寫的程序要求在任何情況下不能崩潰(稱為健壯性)。
如果準確性和健壯性都滿足,接下來,就要考慮最重要的一點:通過算法編寫的程序,運行的效率怎麽樣。
運行效率體現在兩方面:
- 算法的運行時間。(稱為“時間復雜度”)
- 運行算法所需的內存空間大小。(稱為“空間復雜度”)
好算法的標準就是:在符合算法本身的要求的基礎上,使用算法編寫的程序運行的時間短,運行過程中占用的內存空間少,就可以稱這個算法是“好算法”。
時間復雜度的計算
計算一個算法的時間復雜度,不可能把所有的算法都編寫出實際的程序出來讓計算機跑,這樣會做很多無用功,效率太低。實際采用的方法是估算算法的時間復雜度。
在學習C語言的時候講過,程序由三種結構構成:順序結構、分支結構和循環結構。順序結構和分支結構中的每段代碼只運行一次;循環結構中的代碼的運行時間要看循環的次數。
由於是估算算法的時間復雜度,相比而言,循環結構對算法的執行時間影響更大。所以,算法的時間復雜度,主要看算法中使用到的循環結構中代碼循環的次數(稱為“頻度”)。次數越少,算法的時間復雜度越低。
例如:
a) ++x; s=0;
b) for (int i=1; i<=n; i++) { ++x; s+=x; }
c) for (int i=1; i<=n; i++) { for (int j=1; i<=n; j++) { ++x; s+=x; } }
上邊這個例子中,a 代碼的運行了 1 次,b 代碼的運行了 n 次,c 代碼運行了 n*n 次。
時間復雜度的表示
算法的時間復雜度的表示方式為:
O(頻度)
這種表示方式稱為大“O”記法
。
註意,是大寫的字母O
,不是數字0
。
對於上邊的例子而言,a 的時間復雜度為O(1)
,b 的時間復雜度為O(n)
,c 的時間復雜度為為O(n2)
。
如果a、b、c組成一段程序,那麽算法的時間復雜度為O(n2+n+1)
。但這麽表示是不對的,還需要對n2+n+1
進行簡化。
簡化的過程總結為3步:
- 去掉運行時間中的所有加法常數。(例如 n2+n+1,直接變為 n2+n)
- 只保留最高項。(n2+n 變成 n2)
- 如果最高項存在但是系數不是1,去掉系數。(n2 系數為 1)
所以,最終a、b和c合並而成的代碼的時間復雜度為O(n2)
。
常用的時間復雜度的排序
列舉了幾種常見的算法時間復雜度的比較(又小到大):
O(1)常數階
< O(logn)對數階
< O(n)線性階
< O(n2)平方階
< O(n3)(立方階)
< O(2n) (指數階)
拿時間換空間,用空間換時間
算法的時間復雜度和空間復雜度是可以相互轉化的。
谷歌瀏覽器相比於其他的瀏覽器,運行速度要快。是因為它占用了更多的內存空間,以空間換取了時間。
算法中,例如判斷某個年份是否為閏年時,如果想以時間換取空間,算法思路就是:當給定一個年份時,判斷該年份是否能被4或者400整除,如果可以,就是閏年。
如果想以空間換時間的話,判斷閏年的思路就是:把所有的年份先判斷出來,存儲在數組中(年份和數組下標對應),如果是閏年,數組值是1,否則是0;當需要判斷某年是否為閏年時,直接看對應的數組值是1還是0,不用計算就可以馬上知道。
算法時間復雜度和空間復雜度的計算