首先每一個程序運行過程當中,都要佔用必定的計算機資源,好比內存,磁盤等,這些是空間,計算過程當中須要判斷,循環執行某些邏輯,周而反覆,這些是時間。算法
那麼一個算法有多好,多快,怎麼衡量一個算法的好壞?因此,計算機科學在算法分析過程當中,提出了算法複雜度理論,這套理論能夠量化算法的效率,以此做爲標準,方便咱們能衡量到底選擇哪種算法。segmentfault
複雜度有兩個維度:時間和空間。數組
咱們說,一個實現了某算法的程序:數據結構
咱們要選擇複雜度低的算法,衡量好空間和時間的消耗,選出適合特定場景的算法。併發
這兩個複雜度維度的量化過程都是同樣的,因此咱們這裏主要介紹時間複雜度。數據結構和算法
咱們要計算公式1 + 2 + 3 + ... + 100
,那麼按照最直觀的算法來寫:函數
package main import "fmt" func sum(n int) int { total := 0 // 從1加到N, 1+2+3+4+5+..+N for i := 1; i <= n; i++ { total = total + i } return total } func main() { fmt.Println(sum(100)) }
當n = 10
時就等於咱們要計算的公式。這個算法要循環n-1
次,當n
很小時,計算很快,但當n
無限大的時候,計算很慢。spa
因此,算法衡量要衡量的是在不一樣問題規模 n
下,算法的速度。code
在這裏,由於要循環計算n-1
次,而當n
無限大時,常數項基本忽略不計,因此這個算法的時間複雜度,咱們用O(n)
來表示。協程
咱們有另一種計算方式:
func sum2(n int) int { total := ((1 + n) * n) / 2 return total }
此次算法只需執行1
次,因此這個算法的時間複雜度是O(1)
。能夠看出,時間複雜度爲O(1)
的算法優於複雜度爲O(n)
的算法。
固然,還有指數級別的好比以前的漢諾塔算法,對數級別的,階乘級別的複雜度,如O(2^n)
,O(n!)
,O(logn)
等。
算法的優先級排列以下,通常排在上面的要優於排在下面的:
O(1)
O(logn)
O(n)
O(nlogn)
O(n^2)
,O(n^3)
O(2^n)
O(n!)
O(n^n)
如何量化一個複雜度,到底有多複雜,計算機科學抽象出了幾個複雜度漸進符號。
漸進符號以下:
O
,ο
,Θ
,Ω
,ω
分別讀做:Omicron(大歐),omicron(小歐),Theta(西塔),Omega(大歐米伽),omega(小歐米伽)。
假設算法A
的運行時間表達式:
T(n)= 5 * n^3 + 4 * n^2
若是問題規模n
足夠大,那麼低次方的項將無足輕重,運行時間主要取決於高次方的第一項:5*n^3
。
隨着n
的增大,第一項的5*n^3
中的常數5
也無足輕重了。
因此算法A
的運行時間T(n)
約等於n^3
。記爲:
T(n) = Θ(n^3)
Θ
的數學含義:
設f(n)
和g(n)
是定義域n
爲天然數集合的函數,兩個函數同階,也就是當n
無窮大時,f(n)/g(n)
等於某個大於0的常數c
。
也能夠說,存在正常量c1
,c2
和n0
,對於全部n >= n0
,有0 <= c1 * g(n) <= f(n) <= c2 * g(n)
。
那麼能夠記f(n) = Θ(g(n))
,g(n)
是f(n)
的漸進緊確界。
O
的數學含義:
設f(n)
和g(n)
是定義域n
爲天然數集合的函數,f(n)
函數的階不高於g(n)
函數的階。
也能夠說,存在正常量c
和n0
,對於全部n >= n0
,有0 <= f(n) <= c * g(n)
。
那麼能夠記f(n) = O(g(n))
。g(n)
是f(n)
的漸進上界。
Ω
的數學含義:
設f(n)
和g(n)
是定義域n
爲天然數集合的函數,f(n)
函數的階不低於g(n)
函數的階。
也能夠說,存在正常量c
和n0
,對於全部n >= n0
,有0 <= cg(n) <= f(n)
。
那麼能夠記f(n) = Ω(g(n))
。g(n)
是f(n)
的漸進下界。
上面的定義很複雜,咱們能夠來看圖:
當n
值超過某個值時,f(n)
被g(n)
兩條線夾在中間,那麼g(n)
就是漸進緊確界。
若是g(n)
的線在上面,就是漸進上界。
若是g(n)
線在下面,就是漸進下界。
咱們通常會評估一個算法的漸進上界O
,由於這表示算法的最壞狀況,這個上界能夠十分不許確,但咱們通常會評估得足夠準確,好比:
設 f(n) = 5 * n^3 + 4 * n^2,咱們要求漸進上界。
那麼:
f(n) = O(n^3),g(n) = n^3 f(n) = O(n^4),g(n) = n^4
兩個g(n)
都是上界,由於令c = 5
時都存在:0 <= f(n) <= c * g(n))
。
咱們會取乘方更小的那個,由於這個界更逼近f(n)
自己,因此咱們通常說f(n) = O(n^3)
,算法的複雜度爲大歐n
的三次方,表示最壞狀況。
同理,漸進下界Ω
恰好與漸進上界相反,表示最好狀況。好比仍是這個假設:
設 f(n) = 5 * n^3 + 4 * n^2,咱們要求漸進下界。
那麼:
f(n) = Ω(n^3),g(n) = n^3 f(n) = Ω(n^2),g(n) = n^2
兩個g(n)
都是下界,由於令c =5
時都存在:0 <= cg(n) <= f(n)
。
咱們準確評估的時候,要取乘方更大的那個,由於這個界更逼近f(n)
自己,因此咱們通常說f(n) = Ω(n^3)
,算法的複雜度爲大歐米伽n
的三次方,表示最好狀況。
咱們發現當f(n) = Ω(n^3) = O(n^3)
時,其實f(n) = Θ(n)
。
另外兩個漸進符號ο
和ω
通常不多使用,指不那麼緊密的上下界。
也就是評估的時候,不那麼準確去評估,在評估最壞狀況的時候使勁地往壞了評估,評估最好狀況則使勁往好的評估,可是它不能剛恰好,好比上面的結果:
f(n) = O(n^3),g(n) = n^3 f(n) = O(n^4),g(n) = n^4 f(n) = Ω(n^3),g(n) = n^3 f(n) = Ω(n^2),g(n) = n^2
咱們能夠說:
f(n) = ο(n^4),g(n) = n^4 往高階的評估,不能同階 f(n) = ω(n^2),g(n) = n^2 往低階的評估,不能同階
咱們通常用O
漸進上界來評估一個算法的時間複雜度,表示逼近的最壞狀況。其餘漸進符合基本不怎麼使用。
我是陳星星,歡迎閱讀我親自寫的 數據結構和算法(Golang實現),文章首發於 閱讀更友好的GitBook。