一文吃透時間複雜度和空間複雜度

學習數據結構和算法的第一步面試

時間複雜度

最多見的時間複雜度有哪幾種

  • O(1):Constant Complexity 常數複雜度
  • O(log n):Logarithmic ComPlexity 對數複雜度
  • O(n):Linear ComPlexity 線性時間複雜度
  • O(n^2):N square ComPlexity 平方
  • O(n^3):N cubic ComPlexity 立方
  • O(2^n):Exponential Growth 指數
  • O(n!):Factorial 階乘

分析時間複雜度的時候是不考慮前邊的係數的,好比說O(1)的話,並不表明它的複雜度是1,也能夠是二、三、4...,只要是常數次的,都用O(1)表示算法

如何來看一段代碼的時間複雜度?

最經常使用的方式就是直接看這段代碼,它根據n的不一樣狀況會運行多少次數組

O(1)
$n=100000;
echo 'hello';

O(?)
$n=100000;
echo 'hello1';
echo 'hello2';
echo 'hello3';

第一段代碼,無論n是多少,都只執行一次,因此它的時間複雜度就是O(1)。第二個其實也同理,咱們不關心繫數是多少。雖然第二段代碼會執行3次echo輸出,可是無論n是多少,它都只執行3次,所以它的時間複雜度也是常數複雜度,也就是O(1)緩存

在看下邊兩段代碼:數據結構

O(n)
for($i = 1; $i <= $n; $i++) {
    echo 'hello';
}

O(n^2)
for($i = 1; $i <= $n; $i++) {
    for($j = 1; $j <= $n; $j++) {
        echo 'hello';
    }
}

這兩段代碼都是隨着n的不一樣,它執行的次數也在發生變化,第一段代碼執行的次數和n是線性關係的,因此它的時間複雜度是O(n)數據結構和算法

第二段代碼是一個嵌套循環,當n爲100的狀況下,裏邊的輸出語句就會執行10000次,所以它的時間複雜度就是O(n^2)。若是第二段代碼中的循環不是嵌套的,而是並列的,那麼它的時間複雜度應該是O(2n),由於前邊的常數係數咱們不關注,所以它的時間複雜度就是O(n)函數

O(log n)
for($i = 1; $i <= $n; $i = $i*2) {
    echo 'hello';
}

O(k^2)

fib($n) {
    if ($n < 2) {
        return $n;
    }
    
    return fib($n-1) + fib($n-2);
}

第一段代碼,當n=4時,循環執行2次,因此循環內部執行的次數和n的關係爲log2(n),所以時間複雜度爲對數複雜度O(logn)。第二段是一個Fibonacci(斐波拉契)數列,這裏是用了遞歸的這種形式,這就牽扯到了遞歸程序在執行的時候,如何計算它的時間複雜度,它的答案是k^n,k是一個常數,是一個指數級的,因此簡單的經過遞歸的方式求Fibonacci數列是很是慢的,它是指數級的時間複雜度。具體指數級的時間複雜度是怎麼獲得的,後邊會詳細說明。下邊看一下各類時間複雜度的曲線學習

從這張圖中能夠看到,當n比較小的時候,在10之內的話,不一樣的時間複雜度其實都差很少。可是若是當n繼續擴大,指數級的增加是很是快的。所以,當咱們在寫程序的時候,若是能優化時間複雜度,好比說從2^n降到n^2的話,從這個曲線來看,當n較大的時候,獲得的收益是很是高的。所以這也告訴咱們,在平時開發業務代碼的時候,必定要對本身的時間和空間複雜度有所瞭解,並且是養成習慣,寫完代碼以後,下意識的分析出這段程序的時間和空間複雜度。優化

從圖中能夠看到,若是你的時間複雜度寫砸了的話,其實帶給公司的機器或者說資源的損耗,隨着n的增大的話,是成百上千的增長,而若是你能簡化的話,對公司來講是節約不少成本的spa

對於不一樣的程序,在寫法當中完成一樣的目標,它可能會致使時間複雜度的不同。下邊看一個簡單的例題

從1加到2一直加到n,求它的和

小學學數學的時候,你們都知道了有兩種方法,方法一的話用程序暴力求解的話,就是從1循環到n累加。這個是一層循環,n爲多少,就執行多少次累加,因此它的時間複雜度就是O(n)

$sum = 0;
for ($i=1; $i <=$n; $i++) {
    $sum += $i;
}

方法二就是使用一個數學的求和公式:

y = n*(n+1)/2

用這個公式,發現程序就只有一行了,因此它的時間複雜度就是O(1)了。因此能夠看到,程序的不一樣方法,最後獲得的結果雖然是同樣的,可是它的時間複雜度很不同

對於遞歸這種,如何分析時間複雜度?

遞歸的話,關鍵就是要了解它的遞歸過程,總共執行了遞歸語句多少次。若是是循環的話,很好理解,n次的循環就執行了n次。那麼遞歸的話,其實它是層層嵌套,其實不少時候,咱們就是把遞歸它的執行順序,畫出一個樹形結構,稱之爲它的遞歸狀態的遞歸樹。之前邊的求Fibonacci(斐波拉契)數列的第n項爲例

Fib:0,1,1,2,3,5,8,13,21...

F(n) = F(n-1)+F(n-2)

我以前面試的時候遇到過這麼一道題,寫的是最最簡單的用遞歸的方式實現

fib($n) {
    if($n < 2) {
        retuen $n;
    }
    
    return fib($n-1)+fib($n-2);
}

前邊有說到它的時間複雜度是O(k^n),那麼怎麼獲得的,能夠分析一下,假設此時n取6,要計算Fib(6),就看這段代碼如何執行

因此,若是想計算F(6)就引出兩個分支,F(5)和F(4),至少多出了兩次運算

若是要計算F(5)可同理獲得,須要結算F(4)和F(3)。若是要計算F(4)可同理獲得,須要結算F(3)和F(2)。這裏就能夠看到兩個現象:

  • 每多展開一層的話,運行的節點數就是上邊一層的兩倍,第一層只有1個節點,第二層有2個節點,第三層就有4個節點,再下一層就是8個節點。因此它每一層的話,它的節點數,也就是執行次數,是成指數增加的。因而可知,到n的時候,它就執行了2^n次
  • 第二個能夠觀察到,有重複的節點出如今了執行的樹裏邊,好比圖中的F(4)和F(3)。若是將這個樹繼續展開,會發現F(4)、F(3)、F(2)都會被計算了不少次

正是由於有這麼多大量冗餘的計算,致使求6個數的Fibonacci數的話,就變成了2^6次方這麼一個時間複雜度。所以在面試中遇到這類題,儘可能別用這種方式寫,不然怕是直接涼涼了。能夠加一個緩存,把這些中間結果可以緩存下來(用數組或哈希存起來,有重複計算的數值,再從裏邊找),或者直接用一個循環來寫

主定理

介紹一個叫主定理的東西,這個定理爲何重要,就是由於這個主定理的話,其實它是用來解決全部遞歸的函數,怎麼來計算它的時間複雜度。主定理自己的話,數學上來證實相對比較複雜(關於主定理能夠參考維基百科:https://zh.wikipedia.org/wiki...

也就是說,任何一個分治或者遞歸的函數,均可以算出它的時間複雜度,怎麼算就是經過這個主定理。自己比較複雜的話,那怎樣化簡爲實際可用的辦法,其實關鍵就是這四種,通常記住就能夠了

通常在各類遞歸的情形的話,有上邊這四種情形,是在面試和平時工做中會用上

二分查找(Binary search):通常發生在一個數列自己有序的時候,要在有序的數列中找到目標數,因此它每次都一分爲二,只查一邊,這樣的話,最後它的時間複雜度是O(logn)

二叉樹遍歷(Binary tree traversal):若是是二叉樹遍歷的話,它的時間複雜度爲O(n)。由於經過主定理能夠知道,它每次要一分爲二,可是每次一分爲二以後,每一邊它是相同的時間複雜度。最後它的遞推公式就變成了圖中T(n)=2T(n/2)+O(1)這樣。最後根據這個主定理就能夠推出它的運行時間爲O(n)。固然這裏也有一個簡化的思考方式,就是二叉樹的遍歷的話,會每個節點都訪問一次,且僅訪問一次,因此它的時間複雜度就是O(n)

二維矩陣(Optimal sorted matrix search):在一個排好序的二維矩陣中進行二分查找,這個時候也是經過主定理能夠得出時間複雜度是O(n),記住就能夠了

歸併排序(merge sort):全部排序最優的辦法就是nlogn,歸併排序的時間複雜度就是O(nlogn)

常見的關於時間複雜度的面試題

二叉樹的遍歷-前序、中序、後序:時間複雜度是多少?

答案是:O(n),這裏的n表明二叉樹裏邊樹的節點的總數,不論是哪一種方式遍歷,每一個節點都有且僅訪問一次,因此它的複雜度是線性於二叉樹的節點總數,也就是O(n)

圖的遍歷:時間複雜度是多少?

答案:O(n),圖中的每個節點也是有且僅訪問一次,所以時間複雜度也是O(n),n爲圖中的節點總數

搜索算法:DFS(深度優先)、BFS(廣度優先)時間複雜度是多少?

答案:O(n),後邊的文章會詳細介紹這兩種算法(n爲搜索空間中的節點總數)

二分查找:時間複雜度是多少?

答案:O(logn)

空間複雜度

空間複雜度和時間複雜度的狀況其實相似,可是它更加的簡單。用最簡單的方式來分析便可。主要有兩個原則:

若是你的代碼中開了數組,那麼數組的長度基本上就是你的空間複雜度。好比你開了一個一維的數組,那麼你的空間複雜度就是O(n),若是開了一個二維的數組,數組長度是n^2,那麼空間複雜度基本上就是n^2

若是是有遞歸的話,那麼它遞歸最深的深度,就是你空間複雜度的最大值。若是你的程序裏邊遞歸中又開了數組,那麼空間複雜度就是二者的最大值

在快速變化的技術中尋找不變,纔是一個技術人的核心競爭力。知行合一,理論結合實踐

相關文章
相關標籤/搜索