圖算法 - 只需「五步」 ,獲取兩節點間的全部路徑(非遞歸方式)

在實現 「圖」 數據結構時,會遇到 「獲取兩點之間是全部路徑」 這個算法問題,網上的資料大多都是利用遞歸算法來實現(見文末的參考文章)。git

咱們知道在 JS 中用遞歸算法很容易會讓調用棧溢出,爲了能在生產環境中使用,必需要用非遞歸方式的去實現。github

通過一番探索,實現的思路主要來自文章 《求兩點間全部路徑的遍歷算法》 ,只是該文中並無給出具體的實現細節,須要本身去實現;最終本文的實現結合相似《算法 - 調度場算法(Shunting Yard Algorithm)》 中所說起的雙棧來完成。面試

一、算法過程

以計算下圖爲例, 節點 3節點 6 全部路徑全部可能的路徑爲 8 條:算法

allpath

咱們具體講一下如何獲取這 8 條路徑的過程。數組

首先準備兩個棧,分別稱爲 主棧輔棧微信

  • 主棧:每一個元素是單個節點(Vertex),用於存放當前路徑上的節點;
  • 輔棧:每一個元素用於存放主棧對應元素的 相鄰節點列表(Vertex Array);該棧是用來輔助 主棧 的,其長度和 主棧 一致;

Step 1: 建棧

v3節點3)放到主棧,同時將 v3 節點的鄰接節點列表 [v1, v7] 放到輔棧中:數據結構

首次建棧

主棧和輔棧壓入讓棧長度增加,我我的稱之爲 建棧(build stack)學習

Step 2: 繼續建棧

建棧後,咱們查看輔棧,其棧頂是節點列表 [v1, v7]ui

查看棧頂

咱們取出節點列表的第一個元素 v1,將其壓入到主棧;同時將剩下的節點列表 [v7] 從新壓回到輔棧:spa

壓棧

同時查詢 v1 的鄰接節點列表是 [v3, v0]因爲 v3 節點已經在主棧裏,須要從這個列表中剔除(這一步很重要),將剔除後的節點列表 [v0] 壓入 輔棧 中:

繼續建棧

這一步也讓主棧和輔棧長度增加了,因此也是 建棧(build stack) 過程

Step 3: 削棧

繼續 Step 2 的建棧過程,直到咱們的主棧棧頂 v7,此時輔棧的棧頂是空列表 []

當主棧是 v7 的時候,輔棧棧頂是空隊列

因爲輔棧的棧頂是空列表 [],因此無法繼續建棧了 —— 這代表這條路徑走到盡頭了都還沒找到目標節點 v6

走到 此路不通 的境地,咱們就須要開始回退,看看來時的路上的其餘岔路。

咱們將主棧棧頂的 v7 彈出,同時也將輔棧的空列表 [] 彈出:

削棧

這一操做將致使 主棧輔棧 長度減小,該過程我我的稱之爲 削棧(cutdown stack)

Step 4:獲取第一條路徑

重複上述的 Step 2Step 3,採起策略:

  • 只要輔棧棧頂是非空列表,咱們就建棧
  • 只要輔棧棧頂是空列表,咱們就削棧

直到主棧的頂部節點是目標節點 v6

主棧棧頂元素是目標元素v6

進行到這裏,咱們停下來觀察一番,發現主棧裏的內容已是一條完整的從 v3v6 的路徑了:

獲取一條從 v3 到 v6 的路徑

咱們輸出當前棧爲數組:['v3', 'v1', 'v0', 'v2', 'v5', 'v6'],該數組就表示 v3 -> v1 -> v0 -> v2 -> v5 -> v6 這條路徑。

進行至此,咱們終於獲取了一條從 v3v6 的路徑。

應該爲本身的努力鼓個掌,已經看到勝利的曙光;接下來加個簡單的循環就能獲取全部的路徑。

Step 5: 獲取全部路徑

重複 Step 2 - Step 4 步驟,採起策略以下:

  • 只要輔棧棧頂是非空列表,咱們就建棧
  • 只要輔棧棧頂是空列表,咱們就削棧
  • 只要主棧棧頂是目標節點,咱們輸出路徑,同時削棧

重複以上過程,直到主棧爲空爲止。

隨着 建棧(build stack)削棧(cutdown stack) 過程的進行,主棧和輔棧不斷變化着,在這個變化的過程當中咱們就能不斷地獲取從 v3v6 的路徑,最終就能夠獲取全部的路徑。

二、代碼實現

2.一、僞代碼

依據上述過程的描述,很方面將文字轉換成僞代碼:

BEGIN

  初始化主棧
  初始化輔棧
  
  首次建棧
  
  WHILE 主棧不爲空 THEN
  
    獲取輔棧棧頂,爲鄰接節點列表
    
    IF 鄰接節點列表不爲空 THEN
      獲取鄰接節點列表首個元素
      將該元素壓入主棧,剩下列表壓入輔棧
      建棧
    ELSE
      削棧
      CONTINUE
    END IF
    
    IF 主棧棧頂元素 === 目標節點 THEN
      獲取一條路徑,保存起來
      削棧
    END IF
    
  END WHILE
  
END

以上是咱們拿無向圖來作範例,實際上該算法也適合有向圖

2.二、實現效果

該雙棧算法的 JS 實現已經寫到代碼庫 ss-graph 中 ,咱們直接拿它來作校驗,實際運行效果以下:

可前往 https://runkit.com/boycgit/ss... 自行修改數據體驗:

運行實際代碼,驗證算法

三、總結

最近在複習 「圖」 這數據結構,在過程當中逐步嘗試書寫代碼去實現箇中算法。可以體會獲得知識點只有通過本身思考和總結後,才能爲以後的融會貫通打下基礎。

在本文的學習總結中,有兩點體會印象較爲深入:

  1. 能用能遞歸解決的問題,通常均可以用 循環 + 棧(Stack) 的方式來解決。
  2. 當不知道算法如何實現的時候,比較適合概括總結的學習方法,即先逐步從簡單場景開始演示,等摸索到其中規律以後再着手去實現。

圖相關的算法還有不少,有不少經典算法,後續有空會將一些經典的算法實現並整理出來,互有裨益。

參考文章

如下是個人公衆號,會時常更新 JS(Node.js) 知識和資訊,歡迎掃碼關注交流。
我的微信公衆號

相關文章
相關標籤/搜索