在實現 「圖」 數據結構時,會遇到 「獲取兩點之間是全部路徑」 這個算法問題,網上的資料大多都是利用遞歸算法來實現(見文末的參考文章)。git
咱們知道在 JS 中用遞歸算法很容易會讓調用棧溢出,爲了能在生產環境中使用,必需要用非遞歸方式的去實現。github
通過一番探索,實現的思路主要來自文章 《求兩點間全部路徑的遍歷算法》 ,只是該文中並無給出具體的實現細節,須要本身去實現;最終本文的實現結合相似《算法 - 調度場算法(Shunting Yard Algorithm)》 中所說起的雙棧來完成。面試
以計算下圖爲例, 節點 3 到 節點 6 全部路徑全部可能的路徑爲 8 條:算法
咱們具體講一下如何獲取這 8 條路徑的過程。數組
首先準備兩個棧,分別稱爲 主棧 和 輔棧:微信
將 v3
(節點3)放到主棧,同時將 v3
節點的鄰接節點列表 [v1, v7]
放到輔棧中:數據結構
主棧和輔棧壓入讓棧長度增加,我我的稱之爲 建棧(build stack)學習
建棧後,咱們查看輔棧,其棧頂是節點列表 [v1, v7]
:ui
咱們取出節點列表的第一個元素 v1
,將其壓入到主棧;同時將剩下的節點列表 [v7]
從新壓回到輔棧:spa
同時查詢 v1
的鄰接節點列表是 [v3, v0]
,因爲 v3
節點已經在主棧裏,須要從這個列表中剔除(這一步很重要),將剔除後的節點列表 [v0]
壓入 輔棧 中:
這一步也讓主棧和輔棧長度增加了,因此也是 建棧(build stack) 過程
繼續 Step 2 的建棧過程,直到咱們的主棧棧頂 v7,此時輔棧的棧頂是空列表 []
:
因爲輔棧的棧頂是空列表 []
,因此無法繼續建棧了 —— 這代表這條路徑走到盡頭了都還沒找到目標節點 v6。
走到 此路不通 的境地,咱們就須要開始回退,看看來時的路上的其餘岔路。
咱們將主棧棧頂的 v7 彈出,同時也將輔棧的空列表 []
彈出:
這一操做將致使 主棧 和 輔棧 長度減小,該過程我我的稱之爲 削棧(cutdown stack)。
重複上述的 Step 2、Step 3,採起策略:
直到主棧的頂部節點是目標節點 v6
:
進行到這裏,咱們停下來觀察一番,發現主棧裏的內容已是一條完整的從 v3
到 v6
的路徑了:
咱們輸出當前棧爲數組:['v3', 'v1', 'v0', 'v2', 'v5', 'v6']
,該數組就表示 v3 -> v1 -> v0 -> v2 -> v5 -> v6
這條路徑。
進行至此,咱們終於獲取了一條從 v3
到 v6
的路徑。
應該爲本身的努力鼓個掌,已經看到勝利的曙光;接下來加個簡單的循環就能獲取全部的路徑。
重複 Step 2 - Step 4 步驟,採起策略以下:
重複以上過程,直到主棧爲空爲止。
隨着 建棧(build stack) 和 削棧(cutdown stack) 過程的進行,主棧和輔棧不斷變化着,在這個變化的過程當中咱們就能不斷地獲取從 v3
到 v6
的路徑,最終就能夠獲取全部的路徑。
依據上述過程的描述,很方面將文字轉換成僞代碼:
BEGIN 初始化主棧 初始化輔棧 首次建棧 WHILE 主棧不爲空 THEN 獲取輔棧棧頂,爲鄰接節點列表 IF 鄰接節點列表不爲空 THEN 獲取鄰接節點列表首個元素 將該元素壓入主棧,剩下列表壓入輔棧 建棧 ELSE 削棧 CONTINUE END IF IF 主棧棧頂元素 === 目標節點 THEN 獲取一條路徑,保存起來 削棧 END IF END WHILE END
以上是咱們拿無向圖來作範例,實際上該算法也適合有向圖。
該雙棧算法的 JS 實現已經寫到代碼庫 ss-graph 中 ,咱們直接拿它來作校驗,實際運行效果以下:
可前往 https://runkit.com/boycgit/ss... 自行修改數據體驗:
最近在複習 「圖」 這數據結構,在過程當中逐步嘗試書寫代碼去實現箇中算法。可以體會獲得知識點只有通過本身思考和總結後,才能爲以後的融會貫通打下基礎。
在本文的學習總結中,有兩點體會印象較爲深入:
圖相關的算法還有不少,有不少經典算法,後續有空會將一些經典的算法實現並整理出來,互有裨益。
如下是個人公衆號,會時常更新 JS(Node.js) 知識和資訊,歡迎掃碼關注交流。