廣度優先算法,深度優先算法和DijKstra算法

我們經常會碰到最短路徑問題,而最短路徑問題的解決方法多種多樣,廣度優先搜索(BFS),深度優先搜索(DFS)和DijKstra算法貌似都能解決這個問題,這裏就簡單介紹一下這些算法,分析一下它們的適用範圍。

一、原理剖析:
1 廣度優先搜索(BFS)
廣度優先搜索依賴的是隊列解決問題。隊列中的每一個節點需要包含記錄以下內容:該節點到起點的距離dist,該節點的前驅節點past,該節點在當前路徑下是否被訪問過visit(0表示沒有訪問過,1表示當前路徑下正在訪問,2表示該節點周圍的所有節點都已經被訪問過)。
編程邏輯大致如下:

初始化:
起點值初始化(past=NULL,dist=0,visit=1)
其他節點值初始化(past=NULL,dist=無窮,visit=0)
起點入隊
循環1:直到隊列中沒有元素
   從隊伍中輸出一個節點作爲當前節點
   循環2:訪問與當前節點連通但是【沒有被訪問過】的節點(visit=0的節點)
      將即將訪問的節點記爲正在訪問的狀態
      將即將訪問的節點的狀態更新(past=當前節點,dist=即將訪問的節點到當前節點的距離,visit=1)
      即將訪問的節點入隊
   將當前節點的visit記爲2(因爲與它連接的所有節點都被訪問過) 

爲什麼需要記錄這些內容?我們逐一解釋:首先,節點到起點的距離很好解釋,這是問題的需求,但是在二叉樹的層序遍歷時,我們僅僅需要將節點輸出,而不需要計算距離,此時不記錄這個內容也沒關係。如果題意不要求輸出最短的路徑,而是隻要求我們記錄最短的路徑是多少,那不記錄前驅節點問題也不大,它不影響最短路徑的求解。記錄該節點在當前路徑下是否被訪問的目的是,避免圖中有環路而造成節點的重複訪問和死循環,但是對於樹形結構,當前節點不可能遍歷以前訪問過的節點,這個內容可以不記錄。綜上,在寫廣度優先算法的代碼時,要依據需求變通,不應教條。

2 深度優先搜索(DFS)
深度優先搜索依賴的是遞歸,你完全可以把深度優先搜索理解爲動態規劃的一種形式。它一樣要記錄:該節點到起點的距離dist,該節點的前驅節點past,該節點在當前路徑下是否被訪問過visit(0表示沒有訪問過,1表示當前路徑下正在訪問,2表示該節點周圍的所有節點都已經被訪問過)。記錄這些值的目的與廣度優先搜索也差不多。
編程邏輯大致如下:

初始化:
所有節點值初始化(past=NULL,dist=無窮,visit=0)
遞歸DFS(當前節點)
  當前路徑正在訪問當前節點(visit=1)
  對與當前節點連通的所有【沒有被訪問過】的節點
      改變即將訪問的節點的狀態(past爲當前節點,dist爲即將訪問的節點到當前節點的距離)
      DFS(即將訪問的節點)
      【如果有環路,這裏還要加一步:如果當前節點的visit不是2,就把visit設爲0,否則被訪問過一次就再也訪問不了了】
  將當前節點的visit記爲2(因爲與它連接的所有節點都被訪問過) 

3 DijKstra算法
DijKstra算法是運籌學中求最短路徑的常規算法,它的中心思想是:讓每個節點記錄它到起點的最短路徑,其實也可以理解爲動態規劃的一種形式。與前面兩種方法相同,DijKstra算法也需要記錄:該節點到起點的距離dist,該節點的前驅節點past。
但是不同的是,不需要記錄該節點是否被訪問過,而是記錄:該節點到起點的最短距離是否已經確定visit(0表示還沒有確定了該節點到起點的最短距離,1表示已經確定該節點到起點的最短距離)。
編程邏輯大致如下:

初始化:
起點初始化(dist=0,past=NULL,visit=1)
其他節點初始化(dist=無窮,past=起點,visit=0)
循環:對於所有節點
  循環:對於所有【不確定到起點最短距離】的節點,找出距離起點最近的節點並記錄距離
  更新找出的節點的狀態(visit=1)
  循環:對於所有【不確定到起點最短距離】的節點
    計算它到剛纔找出的節點的距離
    如果節點經過剛纔找出的節點到起點的距離小於節點直接到達起點的距離
      更新節點的狀態(dist=找出的節點的dist+找出的節點到該節點的距離,past=找出的節點)

這裏寫圖片描述

這裏寫圖片描述

二、優缺點剖析:
首先我們看如下無向圖:假設所有邊的權重都是正數
這裏寫圖片描述
如果我想求從1到5的最短距離,上述描述的三種方法都可以。

我們再看如下情況:
這裏寫圖片描述
假設所有邊的權重都是相同的正數,求從1到3的最短路徑。
我們利用DFS依照節點的id從小到大搜索,只能得到1-2-3這樣的結果,那是因爲1-3這條路徑根本沒有被訪問到

我們再看算法導論一書中給出的BFS和DFS示意圖:
這裏寫圖片描述
上圖是BFS的示意圖,從圖中可以清楚地看出一次BFS的完整過程。我們可以發現:沒有加粗的邊是沒有被訪問過的。
這裏寫圖片描述
上圖是DFS的示意圖,從圖中可以清楚地看出一次DFS的完整過程,我們可以發現:虛線邊是沒有被訪問過的。

到這裏結論已經很清楚了:
DFS和BFS是以遍歷所有節點爲主要目的的算法,他們不一定能遍歷所有的路徑。即使在一些三種方法通用的情況下,DFS和BFS還有一些編程上的繁瑣之處,主要表現在,我們需要額外定義變量存放最短路徑,節點自身攜帶的最短路徑和前驅節點是隨着循環/迭代的進行不斷更新的,它不能在節點中保留最優解。而對於DijKstra算法而言,節點自身保留的就是最優情況,不需要額外定義變量。

綜上所述:我的建議是,針對樹形結構求解類似最短路徑或最小權重和的題目時,DFS和BFS是不錯的選擇,但是當遇到有環狀結構的最短路徑題時應該格外警惕,此時選擇DijKstra算法比較穩妥。


應用分割線:
如果您看到這裏,希望在留言裏留下您見到的DFS,BFS,DijKstra算法在具體問題中的應用,我看到後會更新在這裏。
廣度優先BFS的應用:
二叉樹的層序遍歷

深度優先DFS的應用:
樹的深度求解

DijKstra:
最短路徑

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章