dfs时间复杂度_一文吃透时间复杂度和空间复杂度
學(xué)習(xí)數(shù)據(jù)結(jié)構(gòu)和算法的第一步(公眾號(hào):IT猿圈)
時(shí)間復(fù)雜度
最常見的時(shí)間復(fù)雜度有哪幾種
- 「O(1)」:Constant Complexity 常數(shù)復(fù)雜度
- 「O(log n)」:Logarithmic ComPlexity 對(duì)數(shù)復(fù)雜度
- 「O(n)」:Linear ComPlexity 線性時(shí)間復(fù)雜度
- 「O(n^2)」:N square ComPlexity 平方
- 「O(n^3)」:N cubic ComPlexity 立方
- 「O(2^n)」:Exponential Growth 指數(shù)
- 「O(n!)」:Factorial 階乘
分析時(shí)間復(fù)雜度的時(shí)候是不考慮前邊的系數(shù)的,比如說O(1)的話,并不代表它的復(fù)雜度是1,也可以是2、3、4...,只要是常數(shù)次的,都用O(1)表示
如何來看一段代碼的時(shí)間復(fù)雜度?
最常用的方式就是直接看這段代碼,它根據(jù)n的不同情況會(huì)運(yùn)行多少次
O(1) $n=100000; echo 'hello';O(?) $n=100000; echo 'hello1'; echo 'hello2'; echo 'hello3';第一段代碼,不管n是多少,都只執(zhí)行一次,所以它的時(shí)間復(fù)雜度就是O(1)。第二個(gè)其實(shí)也同理,我們不關(guān)心系數(shù)是多少。雖然第二段代碼會(huì)執(zhí)行3次echo輸出,但是不管n是多少,它都只執(zhí)行3次,因此它的時(shí)間復(fù)雜度也是「常數(shù)復(fù)雜度」,也就是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的不同,它執(zhí)行的次數(shù)也在發(fā)生變化,第一段代碼執(zhí)行的次數(shù)和n是線性關(guān)系的,所以它的時(shí)間復(fù)雜度是「O(n)」。
第二段代碼是一個(gè)嵌套循環(huán),當(dāng)n為100的情況下,里邊的輸出語句就會(huì)執(zhí)行10000次,因此它的時(shí)間復(fù)雜度就是「O(n^2)」。如果第二段代碼中的循環(huán)不是嵌套的,而是并列的,那么它的時(shí)間復(fù)雜度應(yīng)該是O(2n),因?yàn)榍斑叺某?shù)系數(shù)我們不關(guān)注,因此它的時(shí)間復(fù)雜度就是「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); }第一段代碼,當(dāng)n=4時(shí),循環(huán)執(zhí)行2次,所以循環(huán)內(nèi)部執(zhí)行的次數(shù)和n的關(guān)系為log2(n),因此時(shí)間復(fù)雜度為對(duì)數(shù)復(fù)雜度「O(logn)」。第二段是一個(gè)Fibonacci(斐波拉契)數(shù)列,這里是用了遞歸的這種形式,這就牽扯到了遞歸程序在執(zhí)行的時(shí)候,如何計(jì)算它的時(shí)間復(fù)雜度,它的答案是「k^n」,k是一個(gè)常數(shù),是一個(gè)指數(shù)級(jí)的,所以簡單的通過遞歸的方式求Fibonacci數(shù)列是非常慢的,它是指數(shù)級(jí)的時(shí)間復(fù)雜度。具體指數(shù)級(jí)的時(shí)間復(fù)雜度是怎么得到的,后邊會(huì)詳細(xì)說明。下邊看一下各種時(shí)間復(fù)雜度的曲線
從這張圖中可以看到,當(dāng)n比較小的時(shí)候,在10以內(nèi)的話,不同的時(shí)間復(fù)雜度其實(shí)都差不多。但是如果當(dāng)n繼續(xù)擴(kuò)大,指數(shù)級(jí)的增長是非常快的。因此,當(dāng)我們?cè)趯懗绦虻臅r(shí)候,如果能優(yōu)化時(shí)間復(fù)雜度,比如說從2^n降到n^2的話,從這個(gè)曲線來看,當(dāng)n較大的時(shí)候,得到的收益是非常高的。因此這也告訴我們,在平時(shí)開發(fā)業(yè)務(wù)代碼的時(shí)候,一定要對(duì)自己的時(shí)間和空間復(fù)雜度有所了解,而且是養(yǎng)成習(xí)慣,寫完代碼之后,下意識(shí)的分析出這段程序的時(shí)間和空間復(fù)雜度。
從圖中可以看到,如果你的時(shí)間復(fù)雜度寫砸了的話,其實(shí)帶給公司的機(jī)器或者說資源的損耗,隨著n的增大的話,是成百上千的增加,而如果你能簡化的話,對(duì)公司來說是節(jié)約很多成本的
對(duì)于不同的程序,在寫法當(dāng)中完成同樣的目標(biāo),它可能會(huì)導(dǎo)致時(shí)間復(fù)雜度的不一樣。下邊看一個(gè)簡單的例題
從1加到2一直加到n,求它的和小學(xué)學(xué)數(shù)學(xué)的時(shí)候,大家都知道了有兩種方法,方法一的話用程序暴力求解的話,就是從1循環(huán)到n累加。這個(gè)是一層循環(huán),n為多少,就執(zhí)行多少次累加,所以它的時(shí)間復(fù)雜度就是「O(n)」
$sum = 0; for ($i=1; $i <=$n; $i++) {$sum += $i; }方法二就是使用一個(gè)數(shù)學(xué)的求和公式:
y = n*(n+1)/2用這個(gè)公式,發(fā)現(xiàn)程序就只有一行了,所以它的時(shí)間復(fù)雜度就是O(1)了。所以可以看到,程序的不同方法,最后得到的結(jié)果雖然是一樣的,但是它的時(shí)間復(fù)雜度很不一樣
對(duì)于遞歸這種,如何分析時(shí)間復(fù)雜度?
遞歸的話,關(guān)鍵就是要了解它的遞歸過程,總共執(zhí)行了遞歸語句多少次。如果是循環(huán)的話,很好理解,n次的循環(huán)就執(zhí)行了n次。那么遞歸的話,其實(shí)它是層層嵌套,其實(shí)很多時(shí)候,我們就是把遞歸它的執(zhí)行順序,畫出一個(gè)樹形結(jié)構(gòu),稱之為它的遞歸狀態(tài)的遞歸樹。以前邊的求Fibonacci(斐波拉契)數(shù)列的第n項(xiàng)為例
Fib:0,1,1,2,3,5,8,13,21...F(n) = F(n-1)+F(n-2)我之前面試的時(shí)候遇到過這么一道題,寫的是最最簡單的用遞歸的方式實(shí)現(xiàn)
fib($n) {if($n < 2) {retuen $n;}return fib($n-1)+fib($n-2); }前邊有說到它的時(shí)間復(fù)雜度是「O(k^n)」,那么怎么得到的,可以分析一下,假設(shè)此時(shí)n取6,要計(jì)算Fib(6),就看這段代碼如何執(zhí)行
所以,如果想計(jì)算F(6)就引出兩個(gè)分支,F(5)和F(4),至少多出了兩次運(yùn)算
如果要計(jì)算F(5)可同理得到,需要結(jié)算F(4)和F(3)。如果要計(jì)算F(4)可同理得到,需要結(jié)算F(3)和F(2)。這里就可以看到兩個(gè)現(xiàn)象:
- 每多展開一層的話,運(yùn)行的節(jié)點(diǎn)數(shù)就是上邊一層的兩倍,第一層只有1個(gè)節(jié)點(diǎn),第二層有2個(gè)節(jié)點(diǎn),第三層就有4個(gè)節(jié)點(diǎn),再下一層就是8個(gè)節(jié)點(diǎn)。所以它每一層的話,它的節(jié)點(diǎn)數(shù),也就是執(zhí)行次數(shù),是成指數(shù)增長的。由此可見,到n的時(shí)候,它就執(zhí)行了2^n次
- 第二個(gè)可以觀察到,「有重復(fù)的節(jié)點(diǎn)」出現(xiàn)在了執(zhí)行的樹里邊,比如圖中的F(4)和F(3)。如果將這個(gè)樹繼續(xù)展開,會(huì)發(fā)現(xiàn)F(4)、F(3)、F(2)都會(huì)被計(jì)算了很多次
正是因?yàn)橛羞@么多大量冗余的計(jì)算,導(dǎo)致求6個(gè)數(shù)的Fibonacci數(shù)的話,就變成了2^6次方這么一個(gè)時(shí)間復(fù)雜度。因此在面試中遇到這類題,盡量別用這種方式寫,否則怕是直接涼涼了。可以加一個(gè)緩存,把這些中間結(jié)果能夠緩存下來(用數(shù)組或哈希存起來,有重復(fù)計(jì)算的數(shù)值,再從里邊找),或者直接用一個(gè)循環(huán)來寫
主定理
介紹一個(gè)叫主定理的東西,這個(gè)定理為什么重要,就是因?yàn)檫@個(gè)主定理的話,其實(shí)它是用來解決所有遞歸的函數(shù),怎么來計(jì)算它的時(shí)間復(fù)雜度。主定理本身的話,數(shù)學(xué)上來證明相對(duì)比較復(fù)雜(關(guān)于主定理可以參考維基百科:https://zh.wikipedia.org/wiki/%E4%B8%BB%E5%AE%9A%E7%90%86)
也就是說,任何一個(gè)分治或者遞歸的函數(shù),都可以算出它的時(shí)間復(fù)雜度,怎么算就是通過這個(gè)主定理。本身比較復(fù)雜的話,那怎樣化簡為實(shí)際可用的辦法,其實(shí)關(guān)鍵就是這四種,一般記住就可以了
一般在各種遞歸的情形的話,有上邊這四種情形,是在面試和平時(shí)工作中會(huì)用上
「二分查找(Binary search)」:一般發(fā)生在一個(gè)數(shù)列本身有序的時(shí)候,要在有序的數(shù)列中找到目標(biāo)數(shù),所以它每次都一分為二,只查一邊,這樣的話,最后它的時(shí)間復(fù)雜度是「O(logn)」
「二叉樹遍歷(Binary tree traversal)」:如果是二叉樹遍歷的話,它的時(shí)間復(fù)雜度為「O(n)」。因?yàn)橥ㄟ^主定理可以知道,它每次要一分為二,但是每次一分為二之后,每一邊它是相同的時(shí)間復(fù)雜度。最后它的遞推公式就變成了圖中T(n)=2T(n/2)+O(1)這樣。最后根據(jù)這個(gè)主定理就可以推出它的運(yùn)行時(shí)間為O(n)。當(dāng)然這里也有一個(gè)簡化的思考方式,就是二叉樹的遍歷的話,會(huì)「每一個(gè)節(jié)點(diǎn)都訪問一次,且僅訪問一次,所以它的時(shí)間復(fù)雜度就是O(n)」
「二維矩陣(Optimal sorted matrix search)」:在一個(gè)排好序的二維矩陣中進(jìn)行二分查找,這個(gè)時(shí)候也是通過主定理可以得出時(shí)間復(fù)雜度是「O(n)」,記住就可以了
「歸并排序(merge sort)」:所有排序最優(yōu)的辦法就是「nlogn」,歸并排序的時(shí)間復(fù)雜度就是O(nlogn)
常見的關(guān)于時(shí)間復(fù)雜度的面試題
「二叉樹的遍歷-前序、中序、后序:時(shí)間復(fù)雜度是多少?」
答案是:「O(n)」,這里的n代表二叉樹里邊樹的節(jié)點(diǎn)的總數(shù),不管是哪種方式遍歷,每個(gè)節(jié)點(diǎn)都有且僅訪問一次,所以它的復(fù)雜度是線性于二叉樹的節(jié)點(diǎn)總數(shù),也就是O(n)
「圖的遍歷:時(shí)間復(fù)雜度是多少?」
答案:「O(n)」,圖中的每一個(gè)節(jié)點(diǎn)也是有且僅訪問一次,因此時(shí)間復(fù)雜度也是O(n),n為圖中的節(jié)點(diǎn)總數(shù)
「搜索算法:DFS(深度優(yōu)先)、BFS(廣度優(yōu)先)時(shí)間復(fù)雜度是多少?」
答案:「O(n)」,后邊的文章會(huì)詳細(xì)介紹這兩種算法(n為搜索空間中的節(jié)點(diǎn)總數(shù))
「二分查找:時(shí)間復(fù)雜度是多少?」
答案:「O(logn)」
空間復(fù)雜度
空間復(fù)雜度和時(shí)間復(fù)雜度的情況其實(shí)類似,但是它更加的簡單。用最簡單的方式來分析即可。主要有兩個(gè)原則:
如果你的代碼中開了數(shù)組,那么數(shù)組的長度基本上就是你的空間復(fù)雜度。比如你開了一個(gè)一維的數(shù)組,那么你的空間復(fù)雜度就是O(n),如果開了一個(gè)二維的數(shù)組,數(shù)組長度是n^2,那么空間復(fù)雜度基本上就是n^2
如果是有遞歸的話,那么它遞歸最深的深度,就是你空間復(fù)雜度的最大值。如果你的程序里邊遞歸中又開了數(shù)組,那么空間復(fù)雜度就是兩者的最大值
在快速變化的技術(shù)中尋找不變,才是一個(gè)技術(shù)人的核心競爭力。知行合一,理論結(jié)合實(shí)踐
總結(jié)
以上是生活随笔為你收集整理的dfs时间复杂度_一文吃透时间复杂度和空间复杂度的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ajax 跨域请求api_【.NET C
- 下一篇: 怎么测试服务器端口是否对外开放_从零开始