【演算法】深度優先搜尋(DFS)
挺好的一篇文章,講的挺清楚。
深度優先搜尋(DFS)
【演算法入門】
郭志偉@SYSU:raphealguo(at)qq.com
2012/05/12
1.前言
深度優先搜尋(縮寫DFS)有點類似廣度優先搜尋,也是對一個連通圖進行遍歷的演算法。它的思想是從一個頂點V0開始,沿著一條路一直走到底,如果發現不能到達目標解,那就返回到上一個節點,然後從另一條路開始走到底,這種儘量往深處走的概念即是深度優先的概念。
你可以跳過第二節先看第三節,:)
2.深度優先搜尋VS廣度優先搜尋
2.1演示深度優先搜尋的過程
還是引用上篇文章的樣例圖,起點仍然是V0,我們修改一下題目意思,只需要讓你找出一條V0到V6的道路,而無需最短路。
圖2-1 尋找V0到V6的一條路(無需最短路徑)
假設按照以下的順序來搜尋:
1.V0->V1->V4,此時到底盡頭,仍然到不了V6,於是原路返回到V1去搜尋其他路徑;
2.返回到V1後既搜尋V2,於是搜尋路徑是V0->V1->V2->V6,,找到目標節點,返回有解。
這樣搜尋只是2步就到達了,但是如果用BFS的話就需要多幾步。
2.2深度與廣度的比較
(你可以跳過這一節先看第三節,重點在第三節)
從上一篇《【演算法入門】廣度/寬度優先搜尋(BFS) 》中知道,我們搜尋一個圖是按照樹的層次來搜尋的。
我們假設一個節點衍生出來的相鄰節點平均的個數是N個,那麼當起點開始搜尋的時候,佇列有一個節點,當起點拿出來後,把它相鄰的節點放進去,那麼佇列就有N個節點,當下一層的搜尋中再加入元素到佇列的時候,節點數達到了N2,你可以想想,一旦N是一個比較大的數的時候,這個樹的層次又比較深,那這個佇列就得需要很大的記憶體空間了。
於是廣度優先搜尋的缺點出來了:在樹的層次較深&子節點數較多的情況下,消耗記憶體十分嚴重。廣度優先搜尋適用於節點的子節點數量不多,並且樹的層次不會太深的情況。
那麼深度優先就可以克服這個缺點,因為每次搜的過程,每一層只需維護一個節點。但回過頭想想,廣度優先能夠找到最短路徑,那深度優先能否找到呢?深度優先的方法是一條路走到黑,那顯然無法知道這條路是不是最短的,所以你還得繼續走別的路去判斷是否是最短路?
於是深度優先搜尋的缺點也出來了:難以尋找最優解,僅僅只能尋找有解。其優點就是記憶體消耗小,克服了剛剛說的廣度優先搜尋的缺點。
3.深度優先搜尋
3.1.舉例
給出如圖3-1所示的圖,求圖中的V0出發,是否存在一條路徑長度為4的搜尋路徑。
圖3-1
顯然,我們知道是有這樣一個解的:V0->V3->V5->V6。
3.2.處理過程
3.3.對應例子的虛擬碼
這裡先給出上邊處理過程的對應虛擬碼。
/**
* DFS核心虛擬碼
* 前置條件是visit陣列全部設定成false
* @param n 當前開始搜尋的節點
* @param d 當前到達的深度,也即是路徑長度
* @return 是否有解
*/
bool DFS(Node n, int d){
if (d == 4){//路徑長度為返回true,表示此次搜尋有解
return true;
}
for (Node nextNode in n){//遍歷跟節點n相鄰的節點nextNode,
if (!visit[nextNode]){//未訪問過的節點才能繼續搜尋
//例如搜尋到V1了,那麼V1要設定成已訪問
visit[nextNode] = true;
//接下來要從V1開始繼續訪問了,路徑長度當然要加
if (DFS(nextNode, d+1)){//如果搜尋出有解
//例如到了V6,找到解了,你必須一層一層遞迴的告訴上層已經找到解
return true;
}
//重新設定成未訪問,因為它有可能出現在下一次搜尋的別的路徑中
visit[nextNode] = false;
}
//到這裡,發現本次搜尋還沒找到解,那就要從當前節點的下一個節點開始搜尋。
}
return false;//本次搜尋無解
}
3.4.DFS函式的呼叫堆疊
此後堆疊呼叫返回到V0那一層,因為V1那一層也找不到跟V1的相鄰未訪問節點
此後堆疊呼叫返回到V3那一層
此後堆疊呼叫返回到主函式呼叫DFS(V0,0)的地方,因為已經找到解,無需再從別的節點去搜別的路徑了。
4.核心程式碼
這裡先給出DFS的核心程式碼。
/**
* DFS核心虛擬碼
* 前置條件是visit陣列全部設定成false
* @param n 當前開始搜尋的節點
* @param d 當前到達的深度
* @return 是否有解
*/
bool DFS(Node n, int d){
if (isEnd(n, d)){//一旦搜尋深度到達一個結束狀態,就返回true
return true;
}
for (Node nextNode in n){//遍歷n相鄰的節點nextNode
if (!visit[nextNode]){//
visit[nextNode] = true;//在下一步搜尋中,nextNode不能再次出現
if (DFS(nextNode, d+1)){//如果搜尋出有解
//做些其他事情,例如記錄結果深度等
return true;
}
//重新設定成false,因為它有可能出現在下一次搜尋的別的路徑中
visit[nextNode] = false;
}
}
return false;//本次搜尋無解
}
當然了,這裡的visit陣列不一定是必須的,在一會我給出的24點例子中,我們可以看到這點,這裡visit的存在只是為了保證記錄節點不被重新訪問,也可以有其他方式來表達的,這裡只給出核心思想。
深度優先搜尋的演算法需要你對遞迴有一定的認識,重要的思想就是:抽象!
可以從DFS函式裡邊看到,DFS裡邊永遠只處理當前狀態節點n,而不去關注它的下一個狀態。
它通過把DFS方法抽象,整個邏輯就變得十分的清晰,這就是遞迴之美。
5.另一個例子:24點
5.1.題目描述
想必大家都玩過一個遊戲,叫做“24點”:給出4個整數,要求用加減乘除4個運算使其運算結果變成24,4個數字要不重複的用到計算中。
例如給出4個數:1、2、3、4。我可以用以下運算得到結果24:
1*2*3*4 = 24;2*3*4/1 = 24;(1+2+3)*4=24;……
如上,是有很多種組合方式使得他們變成24的,當然也有無法得到結果的4個數,例如:1、1、1、1。
現在我給你這樣4個數,你能告訴我它們能夠通過一定的運算組合之後變成24嗎?這裡我給出約束:數字之間的除法中不得出現小數,例如原本我們可以1/4=0.25,但是這裡的約束指定了這樣操作是不合法的。
5.2.解法:搜尋樹
這裡為了方便敘述,我假設現在只有3個數,只允許加法減法運算。我繪製瞭如圖5-1的搜尋樹。
圖5-1
此處只有3個數並且只有加減法,所以第二層的節點最多就6個,如果是給你4個數並且有加減乘除,那麼第二層的節點就會比較多了,當延伸到第三層的時候節點數就比較多了,使用BFS的缺點就暴露了,需要很大的空間去維護那個佇列。而你看這個搜尋樹,其實第一層是3個數,到了第二層就變成2個數了,也就是遞迴深度其實不會超過3層,所以採用DFS來做會更合理,平均效率要比BFS快(我沒寫程式碼驗證過,讀者自行驗證)。
6.OJ題目
題目分類來自網路:
sicily:1019 1024 1034 1050 1052 1153 1171 1187
pku:1088 1176 1321 1416 1564 1753 2492 3083 3411
7.總結
DFS適合此類題目:給定初始狀態跟目標狀態,要求判斷從初始狀態到目標狀態是否有解。
8.擴充套件
不知道你注意到沒,在深度/廣度搜尋的過程中,其實相鄰節點的加入如果是有一定策略的話,對演算法的效率是有很大影響的,你可以做一下簡單馬周遊跟馬周遊這兩個題,你就有所體會,你會發現你在搜尋的過程中,用一定策略去訪問相鄰節點會提升很大的效率。
這些運用到的貪心的思想,你可以再看看啟發式搜尋的演算法,例如A*演算法等。
=========================================================
本文為原創,轉載請註明出處:raphealguo@CSDN
作者:raphealguo(at)qq.com
時間:2012/05/12
以上原文!
相關文章
- 基本演算法——深度優先搜尋(DFS)和廣度優先搜尋(BFS)演算法
- 深度優先搜尋演算法-dfs講解演算法
- 《圖論》——深度優先搜尋演算法(DFS)圖論演算法
- 深度優先搜尋演算法(DFS)講解演算法
- 工作安排(dfs深度優先搜尋)
- 深度優先搜尋(DFS)思路及演算法分析演算法
- LeetCode演算法練習——深度優先搜尋 DFSLeetCode演算法
- 學習資料結構 - 深度優先搜尋 DFS 記錄資料結構
- dfs深度優先搜尋解決迷宮類問題(遍歷)
- 深度和廣度優先搜尋演算法演算法
- python 二叉樹深度優先搜尋和廣度優先搜尋Python二叉樹
- 圖的廣度優先搜尋和深度優先搜尋Python實現Python
- 0基礎學演算法 搜尋篇第一講 深度優先搜尋演算法
- 啟發式搜尋的方式(深度優先,廣度優先)和 搜尋方法(Dijkstra‘s演算法,代價一致搜尋,貪心搜尋 ,A星搜尋)演算法
- 深度DFS 和 廣度BFS搜尋演算法學習演算法
- Swift 演算法實戰之路:深度和廣度優先搜尋Swift演算法
- leetcode 刷題之深度優先搜尋LeetCode
- 【知識點】深度優先搜尋 Depth First Search
- DFS深度優先搜尋(3)--hdu2181(哈密頓繞行世界問題)(基礎題)
- 演算法筆記(廣度優先搜尋)演算法筆記
- 演算法競賽——BFS廣度優先搜尋演算法
- 【演算法】廣度/寬度優先搜尋(BFS)演算法
- 演算法(三):圖解廣度優先搜尋演算法演算法圖解
- 再來一篇深度優先遍歷/搜尋總結?
- 廣度優先搜尋(BFS)思路及演算法分析演算法
- JAVA圖搜尋演算法之DFS-BFSJava演算法
- c++ 廣度優先搜尋(寬搜)C++
- 一道經典DFS題(深度優先)-演算法程式設計實踐演算法程式設計
- 0演算法基礎學演算法 搜尋篇第二講 BFS廣度優先搜尋的思想演算法
- 小白的深度優先搜尋(Depth First Search)學習日記(Python)Python
- 二分搜尋樹系列之[ 深度優先-層序遍歷 (ergodic) ]Go
- 二分搜尋樹系列之「深度優先-層序遍歷 (ergodic) 」Go
- js版本的(廣、深)度優先搜尋JS
- Android程式設計師面試會遇到的演算法(part 3 深度優先搜尋-回溯backtracking)Android程式設計師面試演算法
- 【程式碼隨想錄】廣度優先搜尋
- Python 圖_系列之基於鄰接炬陣實現廣度、深度優先路徑搜尋演算法Python演算法
- 寫不出來的深度優先搜尋----leetcode113 路徑總和LeetCode
- POJ 1691 Painting A Board(dfs搜尋)AI