演算法基礎 BFS和DFS的直觀解釋

2021-09-10 07:19:59 字數 2762 閱讀 5221

我們首次接觸 bfs 和 dfs 時,應該是在資料結構課上講的 「圖的遍歷」。還有就是刷題的時候,遍歷二叉樹我們會經常用到bfs和dfs。它們的實現都很簡單,這裡我就不哆嗦去貼**了。

bfs和dfs是圖的兩種遍歷方式,是最簡單的圖搜尋演算法。

bfs和dfs有幾種實現方式,比如: 

1、使用佇列queue實現圖的bfs遍歷

2、遞迴實現圖的dfs遍歷

3、使用棧stack迭代實現圖的dfs遍歷

本篇文章就單純來講講它們的區別和各自的應用,不會涉及任何**。我們以「圖的遍歷」為例,進行說明。

廣度優先搜尋演算法(breadth-first-search,縮寫為 bfs),是一種利用佇列實現的搜尋演算法。簡單來說,其搜尋過程和 「湖面丟進一塊石頭激起層層漣漪」 類似。

深度優先搜尋演算法(depth-first-search,縮寫為 dfs),是一種利用遞迴實現的搜尋演算法。簡單來說,其搜尋過程和 「不撞南牆不回頭」 類似。

bfs 的重點在於佇列,而 dfs 的重點在於遞迴。這是它們的本質區別。

舉個典型例子,如下圖,灰色代表牆壁,綠色代表起點,紅色代表終點,規定每次只能走一步,且只能往下或右走。求一條綠色到紅色的最短路徑。

對於上面的問題,bfs 和 dfs 都可以求出結果,它們的區別就是在複雜度上存在差異。我可以先告訴你,該題 bfs 是較佳演算法。

bfs示意圖:

如上圖所示,從起點出發,對於每次出佇列的點,都要遍歷其四周的點。所以說 bfs 的搜尋過程和 「湖面丟進一塊石頭激起層層漣漪」 很相似,此即 「廣度優先搜尋演算法」 中「廣度」的由來。

dfs示意圖:

如上圖所示,從起點出發,先把乙個方向的點都遍歷完才會改變方向...... 所以說,dfs 的搜尋過程和 「不撞南牆不回頭」 很相似,此即 「深度優先搜尋演算法」 中「深度」的由來。

現在,你不妨對照著圖,再去看看你列印出的遍歷序列,是不是一目了然呢?

最後再說下它們的應用方向。

總的來說,bfs多用於尋找最短路徑的問題,dfs多用於快速發現底部節點。

bfs是用來搜尋最短徑路的解是比較合適的,比如求最少步數的解,最少交換次數的解,因為bfs搜尋過程中遇到的解一定是離最初位置最近的,所以遇到乙個解,一定就是最優解,此時搜尋演算法可以終止,而如果用dfs,會搜一些其他的位置,需要搜很多次,然後還要乙個東西來記錄這次找的位置,之後找到的還要和這次找到的進行比較,這樣就比較麻煩

dfs合搜尋全部的解,因為要搜尋全部的解,在記錄路徑的時候也會簡單一點,而bfs搜尋過程中,遇到離根最近的解,並沒有什麼用,也必須遍歷完整棵搜尋樹。

bfs是捨棄時間換取空間,bfs是捨去空間換取時間。因為dfs要走很多的路徑,可能都是沒用的,(做有些題目的時候要進行剪枝,就是確定不符合條件的就可以推出,以免浪費時間,否則有些題目會tle);而bfs可以走的點要存起來,需要佇列,因此需要空間來儲存,但是快一點。

稍微理解之後就可以了,不一定要糾結怎麼用,先去做題目,很多都是做著就突然明白怎麼用了。

對於dfs,我相信很多人第一次接觸很難設計出相應的演算法,即便是有不錯的程式設計經驗。我第一次幾乎沒辦法設計出解決八皇后的演算法,即便是想了很久。最後沒辦法只好參照別人寫的遞迴式的dfs。之後,雖然對這個演算法有一點了解,但由於了解不夠深度,過了幾天就記不得了,下次又完全不知道怎麼入手。然後需要再到網上搜下**,看一遍後大概才雙知道。而且發現每次寫**的時候心裡總覺得不踏實,一開始總有錯誤的地方,並且每次寫的**都有些不同。總之,寫過很多次後,依然是停留到了解的階段,沒辦法進一步提公升,特別是非遞迴式的dfs一直都停留到靠腦力記憶而不是理解的階段。

今天週末有點時間,覺得有必要解決這些問題,試著花時間去歸納總結dfs的本質,看能否做到一勞永逸。

我設定的目標是:

不僅停留到理解階段,而是要知道這個演算法每一步的實現

捉住其中的本質,給出這個演算法的設計框架。

在1與2的基礎中,可以熟練寫出遞迴與非遞迴兩種實現方式 。

經過乙個下午的研究,我發現任何dfs只需要通過下面幾步就可以實現,無論是遞迴還是非遞迴方式。我給這幾步分別做了乙個命名,分別是find、forward、done、back。

如下:find(right):       在樹的當前層,橫向遍歷,嘗試找到ok的節點。(這一步通常被叫做剪枝,只留下ok的。)

forward(down):     若在當前層找到ok的結點且當前層不是最後一層,則把ok的節點放到當前層;進入下一層第乙個結點,跳到find

其實最重要的是find。然後後面的forward、done、back只是用來控制搜尋走向。這四步可以進一步總結成兩步。

為了了解演算法,我想最好的切入方式是從一些例項開始。下面分別從八皇后以及馬走日等問題做為切入點來分析dfs

1、問題描述

八皇后問題是乙個以西洋棋為背景的問題:如何能夠在8×8 的西洋棋棋盤上放置八個皇后,使得任何乙個皇后都無法直接吃掉其他的皇后?

也就是說,使得棋盤中每個橫向、縱向、左上至右下斜向、右上至左下斜向均只有一枚皇后。

八皇后有92組解,下面給出其中一種解的圖例:

演算法基礎 BFS和DFS的直觀解釋

我們首次接觸 bfs 和 dfs 時,應該是在資料結構課上講的 圖的遍歷 還有就是刷題的時候,遍歷二叉樹我們會經常用到bfs和dfs。它們的實現都很簡單,這裡我就不哆嗦去貼 了。想看 的可以看 劍指offer 三十八 二叉樹的深度 這個題目就可以利用bfs和dfs進行求解。那麼,這兩者 遍歷 的序列...

BFS和DFS直觀區別 FLY

一 前言 我們首次接觸 bfs 和 dfs 時,應該是在資料結構課上講的 圖的遍歷 還有就是刷題的時候,遍歷二叉樹我們會經常用到bfs和dfs。它們的實現都很簡單,這裡我就不哆嗦去貼 了。想看 的可以看 劍指offer 三十八 二叉樹的深度 這個題目就可以利用bfs和dfs進行求解。那麼,這兩者 遍...

DFS和BFS演算法

本質區別 bfs 的重點在於佇列,而 dfs 的重點在於遞迴。這是它們的本質區別。dfs 演算法 是一種利用遞迴 實質上是用棧來儲存未訪問的結點,先進後出 實現的搜尋演算法,直到找到解或走不下去為止。簡單來說,其搜尋過程和 不撞南牆不回頭 樹的先序遍歷 類似。bfs演算法 是一種利用佇列 用佇列來儲...