進階主題 · 圖論 / 尋路

A* 尋路

Dijkstra 加上「方向感」。多算一個「猜還要多遠」,就能朝終點直奔、少走很多冤枉路。

Dijkstra 找最短路時對四面八方一視同仁,像水波往外淹,終點在哪它不在乎。A*(A-star)多帶了一個啟發函數 h —— 對每個點「猜」它到終點還要多遠 —— 於是它會優先往終點的方向找,同樣找到最短路,卻少展開一大堆節點。地圖導航、遊戲裡的怪物追人、機器人路徑,幾乎都是它。一句話:A* = Dijkstra + 一個往終點的方向感。

核心:f = g + h

g(n) —— 已經走的:從起點到 n 實際花的成本(跟 Dijkstra 的 dist 一樣)。

h(n) —— 猜還要走的:從 n 到終點的估計距離(啟發函數,例如格子上的曼哈頓距離)。

f(n) = g(n) + h(n) —— 估計總長:走這條路、經過 n,「大概」總共要多長。

優先佇列挑 f 最小的 —— 跟 Dijkstra 幾乎一模一樣,只是排序的鑰匙從 g 換成 f = g + h。h 恆為 0 就退化回 Dijkstra;h 猜得越準,越像一條直線衝向終點。

它就是 Dijkstra 的一般化。把「只看已經走多遠(g)」改成「看已經走的 + 猜還要走的(g+h)」,就從盲目擴散變成有方向感的搜尋。

什麼時候保證找到最短路?

可採納(admissible):h 永遠不高估 —— 只要 h(n) ≤ 從 n 到終點的真實最短距離,A* 就保證找到最短路。曼哈頓距離對「只能上下左右」的格子就永遠不高估(直線最少要走這麼多步)。

一致(consistent):h 沿邊變化不會太猛 —— 更強的條件,滿足時「節點一出佇列就定案」,不用回頭重算,效率最好。

高估會怎樣? —— h 灌太大,A* 會更快(更貪心地衝),但可能回傳比最短長的路 —— 失去最短保證。要不要用,看你能不能接受「快但不一定最短」。

選跟移動方式匹配的 h —— 只能上下左右用曼哈頓;可以斜走用切比雪夫;連續空間用直線(歐幾里得)距離。挑錯會高估或低估。

複雜度

O(E log V)時間(最壞) —— 跟 Dijkstra 同一級;但 h 好的時候,實際展開的節點少非常多。
O(log V)單次「挑 f 最小」 —— 靠優先佇列(最小堆),跟 Dijkstra 一樣。
O(V)空間 —— open 邊界 + closed 已定案 + g / 前驅,節點級。
A* 的「快」是實務上的快,不是漸進上的快。最壞情況跟 Dijkstra 一樣;它的價值在於「靠 h 少探很多沒必要的節點」。h 越接近真實剩餘距離,越快;h=0 時就完全退回 Dijkstra。

用生活比喻它

🧭

有方向感的找路

Dijkstra 沒方向感,東南西北都探;A* 知道「終點在東邊」,就先往東找,少走冤枉路。

🔥

越靠近越熱

h 就像「離終點越近越熱」的提示,A* 專挑熱的方向挖,不往冷的地方浪費力氣。

🎮

遊戲裡的自動尋路

RTS、RPG 裡的單位點一下就自己繞過障礙走到目的地,底層幾乎都是 A*。

🎮 A* 格子尋路動畫

從 S 走到 G,中間一小塊牆。每一步挑邊界(琥珀)裡 f = g + h 最小的格子展開(格子裡的數字就是 f),綠色是已定案、紫圈是這步正在展開的。看它怎麼幾乎直奔 G、繞過牆又貼回來,不往反方向亂逛。按「下一步」。

10 個程式碼範例(Python)

從格子 A* 基本版、三種啟發函數、還原路徑、h=0 退化成 Dijkstra,到加權 A*、圖版、八方向、可採納性、地形成本,最後一張尋路挑工具表。

顯示範例:

常見陷阱

❌ 用了會高估的 h —— 例如能斜走卻用曼哈頓距離(會高估)。A* 可能回傳非最短的路。要最短,就用不高估的 h。

❌ 沒記 closed / 沒擋重複展開 —— 同一個點被重複挑出來處理,效率崩壞。出佇列時檢查是否已定案就好(或用「過期跳過」那招)。

❌ h 配錯移動方式 —— 只能上下左右卻用直線距離會低估(變慢、退化成 Dijkstra);能斜走卻用曼哈頓會高估(失去最短)。h 要跟著移動規則選。

❌ 以為 A* 一定比 Dijkstra 快 —— h 很爛(≈0)時它就是 Dijkstra;h 亂高估則可能不最短。A* 的好壞全看 h。

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

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