本文轉載自 https://www.jianshu.com/p/f4c...
咱們假設計算機運行一行基礎代碼須要執行一次運算。web
int aFunc(void) { printf("Hello, World!\n"); // 須要執行 1 次 return 0; // 須要執行 1 次 }
那麼上面這個方法須要執行 2 次運算算法
int aFunc(int n) { for(int i = 0; i<n; i++) { // 須要執行 (n + 1) 次 printf("Hello, World!\n"); // 須要執行 n 次 } return 0; // 須要執行 1 次 }
這個方法須要 (n + 1 + n + 1) = 2n + 2 次運算。swift
咱們把 算法須要執行的運算次數 用 輸入大小n 的函數 表示,即 T(n) 。
此時爲了 估算算法須要的運行時間 和 簡化算法分析,咱們引入時間複雜度的概念。函數
定義:存在常數 c 和函數 f(N),使得當 N >= c 時 T(N) <= f(N),表示爲 T(n) = O(f(n)) 。
如圖:測試
當 N >= 2 的時候,f(n) = n^2 老是大於 T(n) = n + 2 的,因而咱們說 f(n) 的增加速度是大於或者等於 T(n) 的,也說 f(n) 是 T(n) 的上界,能夠表示爲 T(n) = O(f(n))。spa
由於f(n) 的增加速度是大於或者等於 T(n) 的,即T(n) = O(f(n)),因此咱們能夠用 f(n) 的增加速度來度量 T(n) 的增加速度,因此咱們說這個算法的時間複雜度是 O(f(n))。設計
算法的時間複雜度,用來度量算法的運行時間,記做: T(n) = O(f(n))。它表示隨着 輸入大小n 的增大,算法執行須要的時間的增加速度能夠用 f(n) 來描述。code
顯然若是 T(n) = n^2,那麼 T(n) = O(n^2),T(n) = O(n^3),T(n) = O(n^4) 都是成立的,可是由於第一個 f(n) 的增加速度與 T(n) 是最接近的,因此第一個是最好的選擇,因此咱們說這個算法的複雜度是 O(n^2) 。orm
那麼當咱們拿到算法的執行次數函數 T(n) 以後怎麼獲得算法的時間複雜度呢?ip
好比 第一個 Hello, World 的例子中 T(n) = 2,因此咱們說那個函數(算法)的時間複雜度爲 O(1)。 T(n) = n + 29,此時時間複雜度爲 O(n)。
好比 T(n) = n^3 + n^2 + 29,此時時間複雜度爲 O(n^3)。
好比 T(n) = 3n^3,此時時間複雜度爲 O(n^3)。
綜合起來:若是一個算法的執行次數是 T(n),那麼只保留最高次項,同時忽略最高項的係數後獲得函數 f(n),此時算法的時間複雜度就是 O(f(n))。爲了方便描述,下文稱此爲 大O推導法。
因而可知,由執行次數 T(n) 獲得時間複雜度並不困難,不少時候困難的是從算法經過分析和數學運算獲得 T(n)。對此,提供下列四個便利的法則,這些法則都是能夠簡單推導出來的,總結出來以便提升效率。
void aFunc(int n) { for(int i = 0; i < n; i++) { // 循環次數爲 n printf("Hello, World!\n"); // 循環體時間複雜度爲 O(1) } }
此時時間複雜度爲 O(n × 1),即 O(n)。
void aFunc(int n) { for(int i = 0; i < n; i++) { // 循環次數爲 n for(int j = 0; j < n; j++) { // 循環次數爲 n printf("Hello, World!\n"); // 循環體時間複雜度爲 O(1) } } }
此時時間複雜度爲 O(n × n × 1),即 O(n^2)。
void aFunc(int n) { // 第一部分時間複雜度爲 O(n^2) for(int i = 0; i < n; i++) { for(int j = 0; j < n; j++) { printf("Hello, World!\n"); } } // 第二部分時間複雜度爲 O(n) for(int j = 0; j < n; j++) { printf("Hello, World!\n"); } }
此時時間複雜度爲 max(O(n^2), O(n)),即 O(n^2)。
void aFunc(int n) { if (n >= 0) { // 第一條路徑時間複雜度爲 O(n^2) for(int i = 0; i < n; i++) { for(int j = 0; j < n; j++) { printf("輸入數據大於等於零\n"); } } } else { // 第二條路徑時間複雜度爲 O(n) for(int j = 0; j < n; j++) { printf("輸入數據小於零\n"); } } }
此時時間複雜度爲 max(O(n^2), O(n)),即 O(n^2)。
時間複雜度分析的基本策略是:從內向外分析,從最深層開始分析。若是遇到函數調用,要深刻函數進行分析。
最後,咱們來練習一下
一. 基礎題
求該方法的時間複雜度
void aFunc(int n) { for (int i = 0; i < n; i++) { for (int j = i; j < n; j++) { printf("Hello World\n"); } } }
參考答案:
當 i = 0 時,內循環執行 n 次運算,當 i = 1 時,內循環執行 n - 1 次運算……當 i = n - 1 時,內循環執行 1 次運算。
因此,執行次數 T(n) = n + (n - 1) + (n - 2)……+ 1 = n(n + 1) / 2 = n^2 / 2 + n / 2。
根據上文說的 大O推導法 能夠知道,此時時間複雜度爲 O(n^2)。
二. 進階題
求該方法的時間複雜度
void aFunc(int n) { for (int i = 2; i < n; i++) { i *= 2; printf("%i\n", i); } }
參考答案:
假設循環次數爲 t,則循環條件知足 2^t < n。
能夠得出,執行次數t = log(2)(n),即 T(n) = log(2)(n),可見時間複雜度爲 O(log(2)(n)),即 O(log n)。
三. 再次進階
求該方法的時間複雜度
long aFunc(int n) { if (n <= 1) { return 1; } else { return aFunc(n - 1) + aFunc(n - 2); } }
參考答案:
顯然運行次數,T(0) = T(1) = 1,同時 T(n) = T(n - 1) + T(n - 2) + 1,這裏的 1 是其中的加法算一次執行。
顯然 T(n) = T(n - 1) + T(n - 2) 是一個斐波那契數列,經過概括證實法能夠證實,當 n >= 1 時 T(n) < (5/3)^n,同時當 n > 4 時 T(n) >= (3/2)^n。
因此該方法的時間複雜度能夠表示爲 O((5/3)^n),簡化後爲 O(2^n)。
可見這個方法所需的運行時間是以指數的速度增加的。若是你們感興趣,能夠試下分別用 1,10,100 的輸入大小來測試下算法的運行時間,相信你們會感覺到時間複雜度的無窮魅力。
做者:raymondCaptain
連接:https://www.jianshu.com/p/f4c... 來源:簡書 著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。