從頭開始複習算法之讓你徹底搞清楚BFS和DFS

2021-03-02 前端瓶子君

作者:klivitamJ,一個愛好寫作,樂意分享的前端人。

來源:https://www.imooc.com/article/285772

最近又有點學不進去了,不知道是不是天氣熱的緣故哈,沒辦法只好寫一點算法來保持學習的路線不間斷咯。關於BFS和DFS,這是我們在面試的時候經常會遇到的兩個基礎算法,為什麼說基礎呢?因為它理解了之後才10行左右的代碼,你說基礎不基礎?

一、 BFS

BFS,全稱:Breadth First Search。中文翻譯為廣度優先搜索或者是寬度優先搜索,具體是怎麼回事兒呢?
首先來用下面一顆的樹來引入一下廣度優先搜索的實現步驟:


如上圖所示,我們先用一棵樹來引入廣度優先搜索。為什麼要用樹呢?因為我覺得樹來入門是最簡單的,也是最容易理解的。
首先按照廣度優先搜索的方法,我們就應該就是這樣來搜索:
A->B->C->D->E->F
或者是:
A->C->B->F->D->E

我先來從結果分析上面的遍歷結果,我發現廣度優先搜索有這樣的特點對:

通過上面的結果來分析,其實我們很容易發現廣度優先算法有點隊列的意味在裡面。
怎麼理解呢?來看下面一套圖:


首先新建一個隊列,先去查找一下樹的根結點,並且將根結點A放入隊列中。

移出節點A,並且把A的子節點加入到隊列中。

移出節點B,並且把B的子節點加入到隊列中。

依次類推,就將所謂的最後的廣度優先搜索的路線給畫出來了:
最終結果

通過前面的探究,我們大概曉得了廣度優先搜索的一個大致流程,也差不多知道其實現的規則有點類似於隊列。此時可能有人會說了,你只是講解了一個簡單的樹,而我們看到的BFS一般是用圖來展示的。
針對上面的疑問,我們首先來畫一個無向圖。

其實在我的理解裡面:圖和樹最大的區別就是樹有專門的起點,但是圖卻沒有固定的起點。我們可以從任何一個點做起點來去搜索,例如:從A點出發,搜索結果是:
A->B->C->D->E->F
從E點出發:
E-> C->D->F->A->B

這樣,我們其實就可以把之前樹的那一套類似到圖中,只不過圖沒有起點罷了,並且將樹的子層換成了與指定節點相連的節點。這樣類比之下也可以用隊列來實現。具體的代碼如下:

首先我們建立一張圖:

let graph = {
    'A':['B','C'],
    'B':['A','C','D'],
    'C':['A','D','E'],
    'D':['B','C','E'],
    'E':['C','D','F'],
    'F':['E']
}

然後BFS的實現代碼如下:

function bfs(graph,startPoint){
    let queue = [];
    let result = []

    queue.push(startPoint);
    result.push(startPoint);

    while(queue.length>0){
        let point = queue.shift();
        let nodes = graph[point];
        for(let node of nodes){
            if(result.includes(node)) continue;
            result.push(node);
            stack.push(node);
        }
    }
    return result

}

二、DFS

好了,前面談到了廣度優先搜索,那麼什麼是深度優先搜索呢?
首先我來用一套圖集來輔助理解深度優先搜索的歷程:


首先選定起點為A(起點是任意的),然後從A出發,搜索到B

然後再從B出發,搜索到C

再從C出發搜索到E

再從E出發搜索到D,此時整張圖都沒有與D相連但是沒被搜索到的節點了,此時該怎麼辦呢?

如果搜索到沒有可以搜索的節點,就應該回溯到最近存在相鄰可以的節點處繼續搜索。

總之,我對於深度優先搜索的理解就是:

這樣看來,深度優先是不是有一點棧的意思在裡面呢?怎麼理解哦,我們再來看下面一套圖。


首先我們從A出發,將節點A移入棧,然後將A移出棧

將A的子節點C,B移入棧內。

然後將B移出棧,並將與B相連的D,C節點移入棧內

然後將C移出棧,將與C相連的D,E節點移入棧內

然後將E移出棧,將與E相連的F,D節點移入棧內

然後將D移除棧,我們發現並沒有可用的新節點了,就不再移入直接移出。

移出F節點

當我在移除新D節點的時候,發現D節點已經被移出過了。此時我們就將該節點丟棄,同樣後面的節點也是一樣。最後就完成了深度優先搜索。

通過上面的圖級演示是不是很容易就能看出來深度優先搜索的實現原理呢?下面我們用代碼的方式來去演示一下其原理吧:

function dfs(graph,startPoint){
    let stack = [];
    let result = []

    stack.push(startPoint);
    result.push(startPoint);

    while(stack.length>0){
        let point = stack.pop();
        let nodes = graph[point];
        for(let node of nodes){
            if(result.includes(node)) continue;
            result.push(node);
            stack.push(node);
        }


    }
    return result

}

說在最後

說了這麼多,感覺午休的時間都所剩無幾了,感覺自己還是沒有把這部分的內容講清楚。本來想額外寫一點關於廣度優先搜索的更深層次的用法的(作為很多圖形結構的基礎,其實應用還是比較廣的),但我還是需要睡的呀!反正中午看來是說不完了。關於還沒寫完的內容呢,我這裡提及一下:
如果相鄰節點之間的距離相同的情況下,我們想求廣度優先搜索的最短路徑怎麼來求呢?
如果相鄰節點之間的距離不同呢?我們應該如何來計算呢?

歡迎關注「前端瓶子君」,回復「交流」加入前端交流群!
歡迎關注「前端瓶子君」,回復「算法」自動加入,從0到1構建完整的數據結構與算法體系!在這裡,瓶子君不僅介紹算法,還將算法與前端各個領域進行結合,包括瀏覽器、HTTP、V8、React、Vue源碼等。在這裡(算法群),你可以每天學習一道大廠算法編程題(阿里、騰訊、百度、字節等等)或 leetcode,瓶子君都會在第二天解答喲!

相關焦點

  • 圖文詳解 DFS 算法 和 BFS 算法
    深度優先遍歷,廣度優先遍歷簡介 深度優先遍歷深度優先遍歷主要思路是從圖中一個未訪問的頂點 V 開始,沿著一條路一直走到底,然後從這條路盡頭的節點回退到上一個節點,再從另一條路開始走到底...,不斷遞歸重複此過程,直到所有的頂點都遍歷完成,它的特點是不撞南牆不回頭,先走完一條路,再換一條路繼續走。
  • 一道題看透DFS和BFS
    作者丨小小算法來源丨小小算法(xiaoxiaosuanfa)一道題看透DFS和BFS你知道 DFS 和 BFS 是哪幾個英文單詞的縮寫嗎?(手動滑稽)。DFS: Depth First Search (深度優先搜索)BFS: Breadth First Search (廣度優先搜索)DFS 和 BFS 都是常用來遍歷搜索樹或圖的算法。
  • 圖文詳解 DFS 和 BFS
    深度優先遍歷,廣度優先遍歷簡介 深度優先遍歷深度優先遍歷主要思路是從圖中一個未訪問的頂點 V 開始,沿著一條路一直走到底,然後從這條路盡頭的節點回退到上一個節點,再從另一條路開始走到底...,不斷遞歸重複此過程,直到所有的頂點都遍歷完成,它的特點是不撞南牆不回頭,先走完一條路,再換一條路繼續走。
  • 萬字詳述 | 全開源:python寫小遊戲+AI強化學習與傳統DFS/BFS控制分別實現
    我以我在 GitHub 上開源的項目 PiperLiu / Amazing-Brick-DFS-and-DRL 為對象,從零開始與各位朋友分享:如何用 python 寫一個小遊戲 、 如何匹配傳統的深度優先搜索算法來控制 、 如何匹配傳統的廣度優先搜索算法來控制 、 如何匹配深度強化學習算法來控制
  • 算法小專欄開篇:DFS之Fibonaci數列
    從今天開始會給大家介紹一些我在學習軟體算法時期的一些題目或者筆記總結
  • 一文帶你搞懂什麼是圖以及常見的算法實現
    isVisited[w]){ // 如果沒有訪問過dfs(isVisited, w); //繼續遞歸}// 繼續從它的下一個鄰接點開始執行w = getNextNeighbor(i,w);}isVisited[i]) {dfs(isVisited, i);}}}執行結果:廣度優先遍歷(bfs)基本思想:
  • BFS+DFS終結遊戲題目
    示例:輸入:board = [[1,2,3],[4,0,5]]輸出:1解釋:交換 0 和 5 ,1 步完成解決這道題比較關鍵的幾點:0與周圍位置交換後得到一個新的二維矩陣下面開始正文,本題的結構這樣,以[[1,2,3],[4,0,5]]為例:        123405   123045 123450 103425 ...
  • Java數據結構和算法(三)圖的深度優先遍歷算法,附代碼(DFS)
    所謂圖的遍歷,即是對結點的訪問,一個圖會有很多的結點,那麼如何遍歷這些結點,需要特定的策略,一般有兩種訪問策略深度遍歷優先和廣度遍歷優先。
  • Edmond-Karp 最大流算法詳解
    以上三篇前導文章都是在認識和使用最大流這種問題模型,從而進行一些算法思考。但是我們始終沒有關心 Ford-Fulkerson 方法的時間複雜度問題。先來複習一下 Ford-Fulkerson 方法的算法流程:在這條路徑中所有的邊的容量減去這條增廣路的流量 f,並建立容量為 f 的反向邊;在這個算法流程中,為將 「使用 DFS」 進行了加粗,你一定察覺到一些端倪。我們來從這個角度來思考一下:
  • LeetCode 例題精講 | 13 BFS 的使用場景:層序遍歷、最短路徑問題
    DFS 遍歷使用遞歸:void dfs(TreeNode root) {    if (root == null) {        return;    }    dfs(root.left);    dfs
  • 幫你徹底搞清楚基督教那些分支
    不過,今天我決定還是和大家聊一聊這個話題。當然,坐觀君(ID:china_2049)可不會只是把二混子的那篇文章放上來就完事了。這不是我的風格。我會從頭和大家講起。二混子將壓軸登場。圖文並茂,絕對有趣。有硬知識,還有趣漫畫。看完了,你不打個賞都不好意思下課。
  • BFS 算法框架套路詳解
    後臺有很多人問起 BFS 和 DFS 的框架,今天就來說說吧。首先,你要說 labuladong 沒寫過 BFS 框架,這話沒錯,今天寫個框架你背住就完事兒了。但要是說沒寫過 DFS 框架,那你還真是說錯了,其實 DFS 算法就是回溯算法,我們前文 回溯算法框架套路詳解 就寫過了,而且寫得不是一般得好,建議好好複習。
  • DFT、DTFT和DFS你搞清楚了嗎?
    大家好,又到了每日學習的時間了,今天咱們來聊一聊數位訊號處理中DFT、DTFT和DFS的關係,咱們通過幾幅圖來對比,探討一下哦。
  • 有關樹遍歷的javascript實現【前端-總結-leetcode算法題】
    在這裡插入圖片描述前言二月的第一天,總結一下近段時間刷的有關樹遍歷的leetcode算法題,希望寫完這篇文章的我和看完文章的你都有些收穫吧。全篇主要講的是有關樹的遍歷,使用前端javascript語言實現。當然有關樹的操作還有很多需要的深入理解和總結的。今天就暫時先把樹遍歷理解全吧。
  • 算法工程師思維導圖 | 數據結構與算法
    該手冊一共分為數據結構與算法、數學基礎、統計機器學習和深度學習四個部分。點擊這裡查看具體使用指南。xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-by-38/LRU:雙向鍊表https://leetcode-cn.com/problems/lru-cache/Least Recently Used尋找重複數-環的入口https://leetcode-cn.com/problems/find-the-duplicate-number/快慢指針,相遇後,
  • 算法之深度優先搜索
    本文作者:丹丘生文末閱讀原文獲取文中示例代碼一、引言研究算法,寫算法是很枯燥的過程;但是找到規律以及通用之處,會發現它是如此簡單個人喜歡循序漸進,從小問題引出要用的算法;深度優先搜索和廣度優先搜索是搜索算法裡面比較基礎的算法,理解以及實現非常容易,當然說歸說,如何容易,且看下文。
  • 「算法與數據結構」二叉樹之美
    從某個節點開始,可以分為很多個子樹,舉個例子,從B節點開始,即是如此。既然對樹有一定認識後,我們需要了解它的一些術語。基本術語示例:❝輸入: [1,null,2,3]12/3輸出: [1,3,2]❞進階: 遞歸算法很簡單,你可以通過迭代算法完成嗎?
  • 數據結構與算法之拓撲排序
    這麼一看,卡恩算法其實就是BFS的操作先遍歷所有出度為0的頂點,然後遍歷出度為0的所有出度指向的頂點,以此類推。2. DFS(深度優先搜索)深度優先搜索算法就比較隨意了,其實應該稱作為DFS+回溯算法。從一個頂點開始,沿著頂點出度的某一路徑進行深度優先搜索訪問一直訪問到到出度為0的頂點後往前回溯,繼續其他出度頂點的訪問。