A*尋路演算法詳細解讀

欽寧~~~發表於2020-10-07

在學習A*演算法之前,很好奇的是A*為什麼叫做A*。在知乎上找到一個回答,大致意思是說,在A*演算法之前有一種基於啟發式探索的方法來提高Dijkstra演算法的速度,這個演算法叫做A1。後來的改進演算法被稱為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號節點計算G(n) = 3,H(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號,5號父節點為2號(注意這裡5號的父節點是2號,因為5號是被2號加入到open列表中的,且一直未被更新),2號父節點為1號,最終得到檢索路徑為:綠色-1-2-5-6-7-紅色

在這裡插入圖片描述


模擬需要更新F值的情況

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

在這裡插入圖片描述

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

在這裡插入圖片描述


Lua程式碼實現

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

  • 優化效率,採用了map快取,避免多次迴圈遍歷
  • 支援配置移動權重
  • 支援配置是否可以斜向移動,斜向時牆角是否可通行

原始碼請檢視:https://github.com/iwiniwin/ResourceLibrary/blob/master/lua/AStar.lua

相關文章