算法的時間複雜度是衡量一個算法效率的基本方法。在閱讀其餘算法教程書的時候,對於算法的時間複雜度的講解難免有些生澀,難以理解。進而沒法在實際應用中很好的對算法進行衡量。算法
《大話數據結構》一書在一開始也針對算法的時間複雜度進行了說明。這裏的講解就很是明確,言簡意賅,很容易理解。下面經過《大話數據結構》閱讀筆記的方式,經過緣由該書的一些簡單的例子和說明來解釋一下算法的時間複雜度和它的計算方法。數據結構
首先從基本定義下手,來了解一下什麼是「算法的時間複雜度」,《大話數據結構》一書中對算法的時間複雜度定義以下:函數
「算法語句總的執行次數 T(n) 是關於問題規模 n 的函數,進而分析 T(n) 隨 n 的變化狀況並肯定 T(n) 的數量級。算法的時間復 雜度,也就是算法的時間度量,記做:T(n) = O(f(n)) 它表示隨問題規模 n 的增大,算法執行時間的增加率和f(n) 的增加率 相同,稱做算法的漸進時間複雜度,簡稱爲時間複雜度。其中 f(n) 是問題規模 n 的某個函數。」spa
光從定義來理解算法的時間複雜度仍是比較難的,咱們再結合一個簡單的例子來講明。計算 1 + 2 + 3 + 4 + ...... + 100 = ? 這樣的問題想必你們都遇到過,這裏咱們經過 C 語言用最簡單的方法實現一下這個問題的算法。翻譯
int sum = 0, n = 100; //執行了 1 次設計
for (int i = 1; i <= n; i++) { //執行了 n + 1 次orm
sum += i; //執行了 n 次教程
}數學
printf(" sum = %d", sum); //執行了 1 次it
從代碼附加的註釋能夠看到全部代碼都執行了多少次。那麼這寫代碼語句執行次數的總和就能夠理解爲是該算法計算出結果所須要的時間。因此說,上述結算 1 + 2 + 3 + 4 + ...... + 100 = ?的算法所用的時間(算法語句執行的總次數)爲 :
1 + ( n + 1 ) + n + 1 = 2n + 3
而當 n 不斷增大,好比咱們此次所要計算的不是 1 + 2 + 3 + 4 + ...... + 100 = ? 而是 1 + 2 + 3 + 4 + ...... + n = ?其中 n 是一個十分大的數字,那麼因而可知,上述算法的執行總次數(所需時間)會隨着 n 的增大而增長,可是在 for 循環之外的語句並不受 n 的規模影響(永遠都只執行一次)。因此咱們能夠將上述算法的執行總次數簡單的記作:
2n 或者簡記 n
這樣咱們就獲得了咱們設計的計算 1 + 2 + 3 + 4 + ...... + 100 = ?的算法的時間複雜度,咱們把它記做:
O(n)
對於同一個問題,解法一般是不惟一的。好比 1 + 2 + 3 + 4 + ...... + 100 = ?這個問題,還有其餘的很多算法。咱們再來看一個數學家高斯解決這個問題的算法(想必你們都很熟悉這個故事)。
SUM = 1 + 2 + 3 + 4 + ...... + 100
SUM = 100 + 99 + 98 + 97 + ...... + 1
SUM + SUM = 2*SUM = 101 + 101 + 101 + .... + 101 正好 100 個 101
SUM = (100*101)/2 = 5050
一樣咱們將這個解法翻譯成 C 語言代碼:
int n = 100, sum = 0; //執行 1 次
sum = (n*(n + 1))/2; //執行 1 次
printf("sum = %d", sum); //執行 1 次
這樣咱們針對同一個 1 + 2 + 3 + 4 + ...... + 100 = ?問題,不一樣的算法又的到了一個算法的時間複雜度:
O(3) 通常記做 O(1) 咱們後續給出緣由。
從感官上咱們就不難看出,從算法的效率上看,O(3) < O(n) 的,因此高斯的算法更快,更優秀(是最優秀的嗎?)。
這種用個大寫的 O 來表明算法的時間複雜度的記法有個專業的名字叫「大O階」記法。那麼經過對上述的例子進行總結,咱們給出算法的時間複雜度(大O階)的計算方法。
推導「大O階」的步驟:
一、用常數 1 取代運行時間中的全部加法常數。
二、在修改後的運行次數函數中,只保留最高階項。
三、若是最高階項存在且不是 1 ,則去除與這個項相乘的常數。
下面咱們在經過一個有很多 for 循環的例子按照上面給出的推導「大O階」的方法來計算一下算法的時間複雜度。先看一下下面的這個例子的代碼,也是用 C 語言寫的,在註釋上咱們仍然對執行次數進行說明。
int n = 100000; //執行了 1 次
for (int i = 0; i < n; i++) { //執行了 n + 1 次
for (int j = 0; j < n; j++) { //執行了 n*(n+1) 次
printf("i = %d, j = %d\n", i, j); //執行了 n*n 次
}
}
for (int i = 0; i < n; i++) { //執行了 n + 1 次
printf("i = %d", i); //執行了 n 次
}
printf("Done"); //執行了 1 次
上面的代碼嚴格的說不能稱之爲一個算法,畢竟它很「無聊並且莫名其妙」(畢竟算法是爲了解決問題而設計的嘛),先不論這個「算法」能解決什麼問題,咱們看一下它的「大O階」如何推導,仍是先計算一下它的執行總次數:
執行總次數 = 1 + (n + 1) + n*(n + 1) + n*n + (n + 1) + 1 = 2n^2 + 3n + 3 這裏 n^2 表示 n 的 2次方。
按照上面推導「大O階」的步驟咱們先來第一步:「用常數 1 取代運行時間中的全部加法常數」,則上面的算式變爲:
執行總次數 = 2n^2 + 3n + 1 這裏 n^2 表示 n 的2次方
第二步:「在修改後的運行次數函數中,只保留最高階項」。這裏的最高階是 n 的二次方,因此算式變爲:
執行總次數 = 2n^2 這裏 n^2 表示 n 的2次方
第三步:「若是最高階項存在且不是 1 ,則去除與這個項相乘的常數」。這裏 n 的二次方不是 1 因此要去除這個項的相乘常數,算式變爲:
執行總次數 = n^2 這裏 n^2 表示 n 的2次方
所以最後咱們獲得上面那段代碼的算法時間複雜度表示爲: O( n^2 ) 這裏 n^2 表示 n 的2次方。
至此,咱們對什麼是「算法的時間複雜度」和「算法的時間複雜度」表示法「大O階」的推導方法進行了簡單的說明。固然要想在往後的實際工做中快速準確的推導出各類算法的「大O階」咱們還須要進行大量的聯繫,畢竟熟能生巧嘛。最後咱們在把常見的算法時間複雜度以及他們在效率上的高低順序記錄在這裏,是你們對算法的效率有個直觀的認識。
O(1) 常數階 < O(logn) 對數階 < O(n) 線性階 < O(nlogn) < O(n^2) 平方階 < O(n^3) < { O(2^n) < O(n!) < O(n^n) }
最後三項我用大括號把他們括起來是想要告訴你們。若是往後你們設計的算法推導出的「大O階」是大括號中的這幾位,那麼趁早放棄這個算法,在去研究新的算法出來吧。由於大括號中的這幾位即使是在 n 的規模比較小的狀況下仍然要耗費大量的時間,算法的時間複雜度大的離譜,基本上就是「不可用狀態」。
固然了,仍是要推薦一下《大話數據結構》這本書的。對於數據結構入門來講,這本書至關不錯,很「生動活潑」,讀起來也頗有意思!