進階主題 · 圖論

Dijkstra 最短路徑

加權圖找最短路。用優先佇列每次挑最近的、鬆弛鄰居。地圖導航就靠它。

Dijkstra 解決「加權圖上,從起點到每個點的最短距離」(邊的權重不能是負的)。BFS 只適合「每步成本一樣」的圖;一旦邊有不同權重(距離、時間、費用),就要 Dijkstra。它是貪婪的:用優先佇列(最小堆)每次挑「目前距離最短、還沒定案」的節點展開,更新它鄰居的距離(這步叫鬆弛 relaxation)。GPS 導航、網路路由都靠它。

核心觀念:挑最近的 + 鬆弛

距離估計 dist[] —— 起點 0,其餘先當無限大 ∞;過程中不斷變小(越來越準)。

優先佇列挑最近 —— 每次從還沒定案的節點裡,挑 dist 最小的出來處理(用最小堆 O(log V))。

鬆弛 relaxation —— 對它每個鄰居 v:若 dist[u] + 邊權 < dist[v],就更新 dist[v](找到更短的路)。

一出隊就定案 —— 因為權重非負,被挑出來的節點,它的最短距離不可能再更小了。

為什麼「挑最近的先定案」是對的?

權重非負 → 繞遠路只會更長 —— 目前距離最小的那個節點,不可能透過還沒處理的(更遠的)節點再找到更短的路。

所以一出優先佇列就「鎖定」 —— 這是 Dijkstra 貪婪選擇正確性的來源。

有負權重就不成立 —— 負邊可能讓「繞遠路反而更短」,這時要用 Bellman-Ford。

BFS 是 Dijkstra 的特例 —— 所有邊權都是 1 時,優先佇列退化成普通佇列,就是 BFS。

複雜度

O((V+E) log V)時間(二元堆優先佇列) —— 每點出隊一次、每邊鬆弛一次,各帶一個 log V。
O(log V)單次「挑最近」/「更新距離」 —— 靠最小堆(堆積那頁)。
O(V+E)空間 —— 鄰接表 + dist 陣列 + 優先佇列 + 前驅。
記住三條分界:不加權最短路用 BFS(O(V+E));加權、非負用 Dijkstra;有負權重用 Bellman-Ford(O(V·E))。挑錯工具會得到錯答案,不只是慢。

用生活比喻它

🗺️

GPS 導航

找最短車程(每段路時間 / 距離不同),不是最少路口 —— 最少路口才是 BFS。

✈️

最便宜機票

轉機組合裡挑總票價最低,每段航線價格不同。

💧

帶阻力的擴散

從源頭往外淹,先淹到「累積成本最低」的地方,再慢慢往貴的地方推。

🎮 Dijkstra 鬆弛動畫

從 A 出發求到各點最短距離(邊上數字是權重)。每步挑「目前 d 最小、還沒定案」的節點(紫色)展開,對鄰居鬆弛(琥珀是被更新的);定案的節點變綠色。看 B 的估計怎麼從 4 變 3、D 從 6 變 4。按「下一步」。

10 個程式碼範例(Python)

從 heapq 版 Dijkstra、還原路徑、格子最短路,到 Bellman-Ford(負權)、最便宜航班、A* 預告、三種最短路對照。

顯示範例:

常見陷阱

❌ 用在有負權重的圖 —— Dijkstra 的貪婪假設被負邊破壞,會算錯。負權用 Bellman-Ford。

❌ 沒跳過過期紀錄 —— 優先佇列裡會有同一節點的舊(較大)距離,pop 到時要用 d > dist[u] 跳過。

❌ 想去「更新堆裡的舊值」 —— 用 lazy 刪除最簡單:直接 push 新距離,pop 時檢查過期就好。

❌ 把 BFS 當加權最短路 —— BFS 只算「步數」;有權重要用 Dijkstra。

🎯 換你了(先別急著查答案)

把答案打到對話裡,我幫你對。