深度優先搜尋 DFS

2021-07-24 22:55:28 字數 3883 閱讀 3334

深度優先搜尋(縮寫dfs)有點類似廣度優先搜尋,也是對乙個連通圖進行遍歷的演算法。它的思想是從乙個頂點v

0開始,

沿著一條路一直走到底,如果發現不能到達目標解,那就返回到上乙個節點,然後從另一條路開始走到底,這種盡量往深處走的概念即是深度優先的概念。

你可以跳過第二節先看第三節,:)

還是引用上篇文章的樣例圖,起點仍然是v0,我們修改一下題目意思,只需要讓你找出一條v0到v6的道路,而無需最短路。

圖2-1 尋找v0到v

6的一條路(無需最短路徑)

假設按照以下的順序來搜尋:

1.v0->v1->v4,此時到底盡頭,仍然到不了v6,於是原路返回到v1去搜尋其他路徑;

2.返回到v1後既搜尋v2,於是搜尋路徑是v0->v1->v2->v6,,找到目標節點,返回有解。

這樣搜尋只是2步就到達了,但是如果用bfs的話就需要多幾步。

(你可以跳過這一節先看第三節,重點在第三節)

》中知道,我們搜尋乙個圖是按照樹的層次來搜尋的。

我們假設乙個節點衍生出來的相鄰節點平均的個數是n個,那麼當起點開始搜尋的時候,佇列有乙個節點,當起點拿出來後,把它相鄰的節點放進去,那麼佇列就有n個節點,當下一層的搜尋中再加入元素到佇列的時候,節點數達到了n2,你可以想想,一旦n是乙個比較大的數的時候,這個樹的層次又比較深,那這個佇列就得需要很大的記憶體空間了。

於是廣度優先搜尋的缺點出來了:在樹的層次較深&子節點數較多的情況下,消耗記憶體十分嚴重。廣度優先搜尋適用於節點的子節點數量不多,並且樹的層次不會太深的情況。

那麼深度優先就可以克服這個缺點,因為每次搜的過程,每一層只需維護乙個節點。但回過頭想想,廣度優先能夠找到最短路徑,那深度優先能否找到呢?深度優先的方法是一條路走到黑,那顯然無法知道這條路是不是最短的,所以你還得繼續走別的路去判斷是否是最短路?

於是深度優先搜尋的缺點也出來了:難以尋找最優解,僅僅只能尋找有解。其優點就是記憶體消耗小,克服了剛剛說的廣度優先搜尋的缺點。

給出如圖3-1所示的圖,求圖中的v0出發,是否存在一條路徑長度為4的搜尋路徑。

圖3-1

顯然,我們知道是有這樣乙個解的:v0->v3->v5->v6。

這裡先給出上邊處理過程的對應偽**。

cpp**  

/*** dfs核心偽**

* 前置條件是visit陣列全部設定成false

* @param n 當前開始搜尋的節點

* @param d 當前到達的深度,也即是路徑長度

* @return 是否有解

*/bool dfs(node n, int d)  

for (node nextnode in n)  

//重新設定成未訪問,因為它有可能出現在下一次搜尋的別的路徑中

visit[nextnode] = false;  

}  //到這裡,發現本次搜尋還沒找到解,那就要從當前節點的下乙個節點開始搜尋。

}  return

false;//本次搜尋無解

}  

此後堆疊呼叫返回到v0那一層,因為v1那一層也找不到跟v1的相鄰未訪問節點

此後堆疊呼叫返回到v3那一層

此後堆疊呼叫返回到主函式呼叫dfs(v0,0)的地方,因為已經找到解,無需再從別的節點去搜別的路徑了。

這裡先給出dfs的核心**。

cpp**  

/*** dfs核心偽**

* 前置條件是visit陣列全部設定成false

* @param n 當前開始搜尋的節點

* @param d 當前到達的深度

* @return 是否有解

*/bool dfs(node n, int d)  

for (node nextnode in n)  

//重新設定成false,因為它有可能出現在下一次搜尋的別的路徑中

visit[nextnode] = false;  

}  }  

return

false;//本次搜尋無解

}  

當然了,這裡的visit陣列不一定是必須的,在一會我給出的24點例子中,我們可以看到這點,這裡visit的存在只是為了保證記錄節點不被重新訪問,也可以有其他方式來表達的,這裡只給出核心思想。

深度優先搜尋的演算法需要你對遞迴有一定的認識,重要的思想就是:抽象!

可以從dfs函式裡邊看到,dfs裡邊永遠只處理當前狀態節點n,而不去關注它的下乙個狀態。

它通過把dfs方法抽象,整個邏輯就變得十分的清晰,這就是遞迴之美。

想必大家都玩過乙個遊戲,叫做「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,但是這裡的約束指定了這樣操作是不合法的。

這裡為了方便敘述,我假設現在只有3個數,只允許加法減法運算。我繪製了如圖5-1的搜尋樹。

圖5-1

此處只有3個數並且只有加減法,所以第二層的節點最多就6個,如果是給你4個數並且有加減乘除,那麼第二層的節點就會比較多了,當延伸到第三層的時候節點數就比較多了,使用bfs的缺點就暴露了,需要很大的空間去維護那個佇列。而你看這個搜尋樹,其實第一層是3個數,到了第二層就變成2個數了,也就是遞迴深度其實不會超過3層,所以採用dfs來做會更合理,平均效率要比bfs快(我沒寫**驗證過,讀者自行驗證)。

題目分類來自網路:

sicily

:1019 1024 1034 1050 1052 1153 1171 1187

pku:1088 1176 1321 1416 1564 1753 2492 3083 3411

dfs適合此類題目:給定初始狀態跟目標狀態,要求判斷從初始狀態到目標狀態是否有解。

不知道你注意到沒,在深度/廣度搜尋的過程中,其實相鄰節點的加入如果是有一定策略的話,對演算法的效率是有很大影響的,你可以做一下簡單馬周遊

跟馬周遊

這兩個題,你就有所體會,你會發現你在搜尋的過程中,用一定策略去訪問相鄰節點會提公升很大的效率。

這些運用到的貪心的思想,你可以再看看啟發式搜尋的演算法,例如a*演算法等。

深度優先搜尋DFS

作為搜尋演算法的一種,dfs對於尋找乙個解的 np 包括npc 問題作用很大。但是,搜尋演算法畢竟是 時間複雜度是o n 的階乘級演算法,它的效率比較低,在資料規模變大時,這種演算法就顯得力不從心了。關於深度優先搜尋的效率問題,有多種解決方法。最具有通用性的是剪枝 prunning 也就是去除沒有用...

深度優先搜尋(dfs)

深度優先搜尋的一般步驟 1 從頂點v出發,訪問v。2 找出剛才訪問過的頂點的第乙個未被訪問的鄰接點,訪問該頂點。以該頂點為新頂點,重複此步驟,直到剛訪問的頂點沒有沒有未被訪問過的鄰接點為止。3 返回前乙個訪問過的仍有未被訪問過的鄰接點的頂點,找出該頂點的下乙個未被訪問過的鄰接點,訪問該頂點。4 重複...

深度優先搜尋DFS

用棧實現的深度優先搜尋,oj aoj,題號alds1 11 b include include using namespace std static const int n 100 static const int white 0 代表未訪問 static const int gary 1 代表訪問...