1. 程式人生 > >一套圖 搞懂“時間複雜度”

一套圖 搞懂“時間複雜度”

寫在前面:

 

這篇文章是在公眾號: 程式設計師小灰 中釋出的。是我到目前為止所看到的關於時間複雜度介紹的最好的文章,簡介 清晰 明瞭。

所以拿來po出來 僅供學習交流,如侵則刪。

 

現已將此文收錄至: 《資料結構》C語言版 (清華嚴蔚敏考研版) 全書知識梳理


正文: 

 

 

640?wx_fmt=jpeg

640?wx_fmt=jpeg

640?wx_fmt=jpeg

640?wx_fmt=jpeg

640?wx_fmt=jpeg

640?wx_fmt=jpeg

 

640?wx_fmt=png

時間複雜度的意義

 

究竟什麼是時間複雜度呢?讓我們來想象一個場景:某一天,小灰和大黃同時加入了一個公司......

640?wx_fmt=jpeg

一天過後,小灰和大黃各自交付了程式碼,兩端程式碼實現的功能都差不多。大黃的程式碼執行一次要花100毫秒,記憶體佔用5MB。小灰的程式碼執行一次要花100秒,記憶體佔用500MB。於是......

640?wx_fmt=jpeg

640?wx_fmt=jpeg

由此可見,衡量程式碼的好壞,包括兩個非常重要的指標:

1.執行時間;

2.佔用空間。

640?wx_fmt=jpeg

640?wx_fmt=jpeg

 

640?wx_fmt=png

基本操作執行次數

 

關於程式碼的基本操作執行次數,我們用四個生活中的場景,來做一下比喻:

場景1:給小灰一條長10寸的麵包,小灰每3天吃掉1寸,那麼吃掉整個麵包需要幾天?

640?wx_fmt=jpeg

答案自然是 3 X 10 = 30天。

如果麵包的長度是 N 寸呢?

此時吃掉整個麵包,需要 3 X n = 3n 天。

如果用一個函式來表達這個相對時間,可以記作 T(n) = 3n。

場景2:給小灰一條長16寸的麵包,小灰每5天吃掉麵包剩餘長度的一半,第一次吃掉8寸,第二次吃掉4寸,第三次吃掉2寸......那麼小灰把麵包吃得只剩下1寸,需要多少天呢?

這個問題翻譯一下,就是數字16不斷地除以2,除幾次以後的結果等於1?這裡要涉及到數學當中的對數,以2位底,16的對數,可以簡寫為log16。

因此,把麵包吃得只剩下1寸,需要 5 X log16 = 5 X 4 = 20 天。

如果麵包的長度是 N 寸呢?

需要 5 X logn = 5logn天,記作 T(n) = 5logn。

場景3:給小灰一條長10寸的麵包和一個雞腿,小灰每2天吃掉一個雞腿。那麼小灰吃掉整個雞腿需要多少天呢?

640?wx_fmt=jpeg

答案自然是2天。因為只說是吃掉雞腿,和10寸的麵包沒有關係 。

如果麵包的長度是 N 寸呢?

無論麵包有多長,吃掉雞腿的時間仍然是2天,記作 T(n) = 2。

場景4:給小灰一條長10寸的麵包,小灰吃掉第一個一寸需要1天時間,吃掉第二個一寸需要2天時間,吃掉第三個一寸需要3天時間.....每多吃一寸,所花的時間也多一天。那麼小灰吃掉整個麵包需要多少天呢?

答案是從1累加到10的總和,也就是55天。

如果麵包的長度是 N 寸呢?

此時吃掉整個麵包,需要 1+2+3+......+ n-1 + n = (1+n)*n/2 = 0.5n^2 + 0.5n。

記作 T(n) = 0.5n^2 + 0.5n。

640?wx_fmt=jpeg

上面所講的是吃東西所花費的相對時間,這一思想同樣適用於對程式基本操作執行次數的統計。剛才的四個場景,分別對應了程式中最常見的四種執行方式:

場景1:T(n) = 3n,執行次數是線性的。

 
  1. void eat1(int n){

  2.     for(int i=0; i<n; i++){;

  3.         System.out.println("等待一天");

  4.         System.out.println("等待一天");

  5.         System.out.println("吃一寸麵包");

  6.     }

  7. }

  8. vo

場景2:T(n) = 5logn,執行次數是對數的。

 
  1. void eat2(int n){

  2.    for(int i=1; i<n; i*=2){

  3.        System.out.println("等待一天");

  4.        System.out.println("等待一天");

  5.        System.out.println("等待一天");

  6.        System.out.println("等待一天");

  7.        System.out.println("吃一半面包");

  8.    }

  9. }

場景3:T(n) = 2,執行次數是常量的。

 
  1. void eat3(int n){

  2.    System.out.println("等待一天");

  3.    System.out.println("吃一個雞腿");

  4. }

場景4:T(n) = 0.5n^2 + 0.5n,執行次數是一個多項式。

 
  1. void eat4(int n){

  2.    for(int i=0; i<n; i++){

  3.        for(int j=0; j<i; j++){

  4.            System.out.println("等待一天");

  5.        }

  6.        System.out.println("吃一寸麵包");

  7.    }

  8. }

 

640?wx_fmt=png

漸進時間複雜度

 

有了基本操作執行次數的函式 T(n),是否就可以分析和比較一段程式碼的執行時間了呢?還是有一定的困難。

比如演算法A的相對時間是T(n)= 100n,演算法B的相對時間是T(n)= 5n^2,這兩個到底誰的執行時間更長一些?這就要看n的取值了。

所以,這時候有了漸進時間複雜度(asymptotic time complectiy)的概念,官方的定義如下:

若存在函式 f(n),使得當n趨近於無窮大時,T(n)/ f(n)的極限值為不等於零的常數,則稱 f(n)是T(n)的同數量級函式。

記作 T(n)= O(f(n)),稱O(f(n)) 為演算法的漸進時間複雜度,簡稱時間複雜度。

漸進時間複雜度用大寫O來表示,所以也被稱為大O表示法。

640?wx_fmt=jpeg

640?wx_fmt=jpeg

如何推匯出時間複雜度呢?有如下幾個原則:

  1. 如果執行時間是常數量級,用常數1表示;

  2. 只保留時間函式中的最高階項;

  3. 如果最高階項存在,則省去最高階項前面的係數。

讓我們回頭看看剛才的四個場景。

場景1:

T(n) = 3n 

最高階項為3n,省去係數3,轉化的時間複雜度為:

T(n) =  O(n)

640?wx_fmt=png

場景2:

T(n) = 5logn 

最高階項為5logn,省去係數5,轉化的時間複雜度為:

T(n) =  O(logn)

640?wx_fmt=png

場景3:

T(n) = 2

只有常數量級,轉化的時間複雜度為:

T(n) =  O(1)

640?wx_fmt=png

場景4:

T(n) = 0.5n^2 + 0.5n

最高階項為0.5n^2,省去係數0.5,轉化的時間複雜度為:

T(n) =  O(n^2)

640?wx_fmt=png

這四種時間複雜度究竟誰用時更長,誰節省時間呢?稍微思考一下就可以得出結論:

O(1)< O(logn)< O(n)< O(n^2)

在程式設計的世界中有著各種各樣的演算法,除了上述的四個場景,還有許多不同形式的時間複雜度,比如:

O(nlogn), O(n^3), O(m*n),O(2^n),O(n!)

今後遨遊在程式碼的海洋裡,我們會陸續遇到上述時間複雜度的演算法。

640?wx_fmt=png

 

640?wx_fmt=png

時間複雜度的巨大差異

 

 

640?wx_fmt=jpeg

640?wx_fmt=jpeg

我們來舉過一個栗子:

演算法A的相對時間規模是T(n)= 100n,時間複雜度是O(n)

演算法B的相對時間規模是T(n)= 5n^2,時間複雜度是O(n^2)

演算法A執行在小灰家裡的老舊電腦上,演算法B執行在某臺超級計算機上,執行速度是老舊電腦的100倍。

那麼,隨著輸入規模 n 的增長,兩種演算法誰執行更快呢?

640?wx_fmt=png

從表格中可以看出,當n的值很小的時候,演算法A的執行用時要遠大於演算法B;當n的值達到1000左右,演算法A和演算法B的執行時間已經接近;當n的值越來越大,達到十萬、百萬時,演算法A的優勢開始顯現,演算法B則越來越慢,差距越來越明顯。

這就是不同時間複雜度帶來的差距。

640?wx_fmt=jpeg

相關推薦

時間複雜

寫在前面:   這篇文章是在公眾號: 程式設計師小灰 中釋出的。是我到目前為止所看到的關於時間複雜度介紹的最好的文

android事件分發機制

        事件分發機制在安卓中非常重要,這個事情如果搞不懂,會困擾你很多事情。比如說,應用了github上的某個大神的庫,如果發現這個庫實現了你需求的80%,還有那麼20%需要你結合實際需求來實現

陣列中只出現次的數字,時間複雜O(n),空間複雜O(1)的解法

題目:一個整型數組裡除了兩個陣列外,其他的數字都出現了兩次,要找出這兩個數字。      異或運算有一個性質:任何數異或它自己,結果都是0;這樣如果題目變成只有一個數字只出現一次,其他數字均出現兩次,這樣我們從頭到尾異或陣列中的每一個數字,那麼最終的結果就是隻出現一次的數字

Spring bean的完整生命週期

一張圖搞懂Spring bean的生命週期,從Spring容器啟動到容器銷燬bean的全過程,包括下面一系列的流程,瞭解這些流程對我們想在其中任何一個環節怎麼操作bean的生成及修飾是非常有幫助的。 Bean的完整生命

Ajax原理

監聽 javascrip 理解 state 進度 調用 end 最好的 let 本文整理在,我的github上。歡迎Star。 原理 說起ajax,就不得不說他背後的核心對象XMLHttpRequest,而說到XMLHttpRequest我覺得,從它的readyStat

徹底明白了“時間複雜

寫在前面:   這篇文章是在csdn公眾號 程式人生中釋出的。是我到目前為止所看到的關於時間複雜度介紹的最好的文章,簡介 清晰 明瞭。 所以拿來po出來 僅供學習交流,如侵則刪。   正文:     

徹底明白了“時間複雜

寫在前面: 這篇文章是在csdn公眾號 程式人生中釋出的。是我到目前為止所看到的關於時間複雜度介紹的最好的文章,簡介 清晰 明瞭。 所以拿來po出來 僅供學習交流,如侵則刪。 正文:  時間複雜度的意義 究竟什麼是

資料結構 時間複雜 空間複雜 看就版本

時間複雜度 時間複雜度簡單的理解就是執行語句的條數。如果有迴圈和遞迴,則忽略簡單語句,直接算迴圈和遞迴的語句執行次數。 比如: int x = 1;//時間複雜度為O(1) for(int i=0; i<n; i++) {       System.

編寫程式,在非遞減的順序表L中,刪除所有值相等的多餘元素。要求時間複雜O(n),空間複雜為O(1)

翠花上程式碼: Status delExcrescentValue(SeqList &S){ int flag = 0,val = S.elem[0];//記錄值不等的下標 //printf("%d\n",S.elem[0]); for(int i = 1;i

設計一個演算法,將維陣列A(下標從1開始)中的元素迴圈右移k位,要求只用一個元素大小的附加儲存空間。給出演算法的時間複雜

程式碼 #include<stdio.h> #include<stdlib.h> #define n 10 int main() { int a[n] = { 0,1,2,3,4,5,6,7,8,9 }; int k, t=0,i,j,m; printf(

《Python 每日學》之短路法優化時間複雜

昨天在 Python 實戰交流群裡發起一個討論: 在如下這個常見的遍歷場景中,如何優化程式碼降低時間複雜度? def tips_everyday_example(): vector = ['

初學者學演演算法|從時間複雜認識常見演演算法(

O(log n):二分搜尋法時間複雜度為 O(log n) 的演演算法(這邊的 log 都是以二為底),代表當輸入的數量是 n 時,執行的步驟數會是 log n。(讓忘記 log 是什麼的同學們複習一下,當 log n = x 的意思是 n = 2^x,如果這部分的腦細胞尚未復活,且讓我們先記住 n = 2^

看動畫輕鬆理解時間複雜

原文連結:看動畫輕鬆理解時間複雜度(一) 演算法(Algorithm)是指用來操作資料、解決程式問題的一組方法。對於同一個問題,使用不同的演算法,也許最終得到的結果是一樣的,比如排序就有前面的十大經典排序和幾種奇葩排序,雖然結果相同,但在過程中消耗的資源和時間卻會有很大的區別,比如快速排序與猴子排

排序 查詢 樹 時間複雜

查詢演算法 時間複雜度 順序查詢 O(n) 演算法簡單,適應面廣,穩定演算法 折半查詢 O(log2n) 針對有序的序列表,不穩定 分塊查詢 介於順序查詢和折半查詢之間 針對有序表,不穩定演算法 平

(資料結構)十分鐘定時間複雜(演算法的時間複雜)【轉】

我們假設計算機執行一行基礎程式碼需要執行一次運算。 int aFunc(void) { printf("Hello, World!\n"); // 需要執行 1 次 return 0; // 需要執行 1 次 } 那麼上面這個

十分鐘定時間複雜(演算法的時間複雜

我們假設計算機執行一行基礎程式碼需要執行一次運算。 int aFunc(void) { printf("Hello, World!\n"); // 需要執行 1 次 return 0; // 需要執行 1 次 } 那麼上面這個方法

資料結構(時間複雜

資料結構馬上就要考試了,抓緊複習下,聽說資料結構對於這一行業來說挺重要的,所以就把複習的歷程記錄下來,以備後用(順便裝個X)。 1.試分析下面各程式段的時間複雜度。 (1)x=90; y=100; while(y>0) if(

輸入個數組和一個數字,在陣列中查詢兩個數,使得它們的和正好是輸入的那個數字 時間複雜O(NlogN)

/* *[email protected] 轉載請註明出處 *題目:輸入一個數組和一個數字,在陣列中查詢兩個數, *使得它們的和正好是輸入的那個數字。 *如果有多對數字的和等於輸入的數字,輸出任意一對即可。 *例如輸入陣列1、2、4、7、11、15和數字15。由於

重拾演算法():演算法效率分析(空間複雜時間複雜

前言: 演算法效率分析分為兩種:第一種是時間效率,第二種是空間效率。時間效率被稱為時間複雜度,而空間效率被稱作空間複雜度。 時間複雜度主要衡量的是一個演算法的執行速度,而空間複雜度主要衡量一個演算法所需要的額外空間,在計算機發展的早期,計算機的儲存容量很小。所以對空間複雜

演算法(時間複雜

前言 演算法很重要,但是一般情況下做移動開發並不經常用到,所以很多同學早就將演算法打了個大禮包送還給了老師了,況且很多同學並沒有學習過演算法。這個系列就讓對演算法頭疼的同學能快速的掌握基本的演算法。過年放假階段玩了會遊戲NBA2K17的生涯模式,沒有比賽的日子