A*尋路算法詳細解讀


在學習A*算法之前,很好奇的是A*爲什麼叫做A*。在知乎上找到一個回答,大致意思是說,在A*算法之前有一種基於啓發式探索的方法來提高Dijkstra算法的速度,這個算法叫做A1。後來的改進算法被稱爲A*。*這個符號是從統計文獻中借鑑來的,用來表示相對一箇舊有標準的最優估計。

A*尋路算法演示

啓發式探索是利用問題擁有的啓發信息來引導搜索,達到減少探索範圍,降低問題複雜度的目的。

A*尋路算法就是啓發式探索的一個典型實踐,在尋路的過程中,給每個節點綁定了一個估計值(即啓發式),在對節點的遍歷過程中是採取估計值優先原則,估計值更優的節點會被優先遍歷。所以估計函數的定義十分重要,顯著影響算法效率。

A*算法描述

簡化搜索區域

將待搜索的區域簡化成一個個小方格,最終找到的路徑就是一些小方格的組合。當然是可以劃分成任意形狀,甚至是精確到每一個像素點,這完全取決於你的遊戲的需求。一般情況下劃分成方格就可以滿足我們的需求,同時也便於計算。
如下圖區域,被簡化成6*6的小方格。其中綠色表示起點,紅色表示終點,黑色表示路障,不能通行。
簡化地圖

概述算法步驟

先描述A*算法的大致過程:

  1. 將初始節點放入到open列表中。
  2. 判讀open列表。如果爲空,則搜索失敗。如果open列表中存在目標節點,則搜索成功。
  3. 從open列表中取出F值最小的節點作爲當前節點,並將其加入到close列表中。
  4. 計算當前節點的相鄰的所有可到達節點,生成一組子節點。對於每一個子節點:
    • 如果該節點在close列表中,則丟棄它
    • 如果該節點在open列表中,則檢查其通過當前節點計算得到的F值是否更小,如果更小則更新其F值,並將其父節點設置爲當前節點。
    • 如果該節點不在open列表中,則將其加入到open列表,並計算F值,設置其父節點爲當前節點。
  5. 轉到2步驟

進一步解釋

初始節點,目標節點,分別表示路徑的起點和終點,相當於上圖的綠色節點和紅色節點
F值,就是前面提到的啓發式,每個節點都會被綁定一個F值
F值是一個估計值,用F(n) = G(n) + H(n) 表示,其中G(n)表示由起點到節點n的固定消耗,H(n)表示節點n到終點的估計消耗。H(n)的計算方式有很多種,比如曼哈頓H(n) = x + y,或者歐幾里得式H(n) = sqrt(x^2 + y^2)。本例中採用曼哈頓式。
F(n)就表示由起點經過n節點到達終點的總消耗
爲了便於描述,本文在每個方格的左下角標註數字表示G(n),右下角數字表示H(n),左上方數字表示F(n)。具體如何計算請看下面的一個例子

具體尋路過程

接下來,我們嚴格按照A*算法找出從綠色節點到紅色節點的最佳路徑
首先將綠色節點加入到open列表中
接着判斷open列表不爲空(有起始節點),紅色節點不在open列表中
然後從open列表中取出F值最小的節點,此時,open列表中只有綠色節點,所以將綠色節點取出,作爲當前節點,並將其加入到close列表中
計算綠色節點的相鄰節點(暫不考慮斜方向移動),如下圖所示的所有灰色節點,並計算它們的F值。這些子節點既沒有在open列表中,也沒有在close列表中,所以都加入到open列表中,並設置它們的父節點爲綠色節點

F值計算方式
以綠色節點右邊的灰色節點爲例
G(n) = 1,從綠色節點移動到該節點,都只需要消耗1步
H(n) = 3,其移動到紅色節點需要消耗橫向2步,豎向一步,所以共消耗3步(曼哈頓式)
F(n) = 4 = G(n) + H(n)

試着算一下其他灰色節點的F值吧,看看與圖上標註的是否一致

繼續選擇open列表中F值最小的節點,此時最小節點有兩個,都爲4。這種情況下選取哪一個都是一樣的,不會影響搜索算法的效率。因爲啓發式相同。這個例子中按照右下左上的順序選取(這樣可以少畫幾張圖(T▽T))。先選擇綠色節點右邊的節點爲當前節點,並將其加入close列表。其相鄰4個節點中,有1個是黑色節點不可達,綠色節點已經被加入close列表,還剩下上下兩個相鄰節點,分別計算其F值,並設置他們的父節點爲黃色節點。

此時open列表中F值最小爲4,繼續選取下方節點,計算其相鄰節點。其右側是黑色節點,上方1號節點在close列表。下方節點是新擴展的。主要來看下左側節點,它已經在open列表中了。根據算法我們要重新計算它的F值,按經過2號節點計算H(n) = 3,G(n)不變,所以F(n) = 6相比於原值反而變大了,所以什麼也不做。(後面的步驟中重新計算F值都不會更小,不再贅述)

此時open列表中F值最小仍爲4,繼續選取

此時open列表中F值最小爲6,優先選取下方節點

此時open列表中F值最小爲6,優先選取右方節點

此時open列表中F值最小爲6,優先選取右方節點

此時open列表中F值最小爲6,優先選取右方節點

此時我們發現紅色節點已經被添加到open列表中,算法結束。從紅色節點開始逆推,其父節點爲7號,7號父節點爲6號,6號父節點爲5號…,最終得到檢索路徑爲:綠色-1-2-5-6-7-紅色


模擬需要更新F值的情況

在上面的例子中,所有遇到已經在open列表中的節點重新計算F值都不會更小,無法做更新操作。
所以再舉一個例子來演示這種情況。相同的搜索區域,假設豎向或橫向移動需要消耗1,這次也支持斜方向移動了,但是斜方向可能都是些山路不好走,移動一次需要消耗4。對應的相鄰節點F值如下圖所示

同樣選擇open列表中F值最小的節點,我們優先選擇了右方節點,計算其相鄰節點。共8個。其中三個是黑色節點,一個綠色節點在close列表中,不考慮。上方兩個和下方兩個都是已經在open列表中了,要重新計算F值。
先看左上角的相鄰節點,通過黃色節點到達改節點,H(n) = 5,G(n)不變,F(n)反而更大了,所以什麼也不做。左下角節點同理。
上方居中節點,通過黃色節點計算H(n) = 2, G(n)不變,F(n) = 6 < 8 所以,更新這個節點的F值,並將其父節點修改爲黃色節點。下方居中節點同理。


Lua代碼實現

寫了一套A*算法的Lua實現。主要特點如下:

  • 優化效率,採用了map緩存,避免多次循環遍歷

  • 支持配置移動權重

  • 支持配置是否可以斜向移動,斜向時牆角是否可通行

源碼請查看:https://github.com/iwiniwin/LuaKit/blob/master/AStar.lua

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