A*尋路演算法詳細解讀
在學習A*演算法之前,很好奇的是A*為什麼叫做A*。在知乎上找到一個回答,大致意思是說,在A*演算法之前有一種基於啟發式探索的方法來提高Dijkstra演算法的速度,這個演算法叫做A1。後來的改進演算法被稱為A*。*這個符號是從統計文獻中借鑑來的,用來表示相對一箇舊有標準的最優估計。
啟發式探索是利用問題擁有的啟發資訊來引導搜尋,達到減少探索範圍,降低問題複雜度的目的。
A*尋路演算法就是啟發式探索的一個典型實踐,在尋路的過程中,給每個節點繫結了一個估計值(即啟發式),在對節點的遍歷過程中是採取估計值優先原則,估計值更優的節點會被優先遍歷。所以估計函式的定義十分重要,顯著影響演算法效率。
A*演算法描述
簡化搜尋區域
將待搜尋的區域簡化成一個個小方格,最終找到的路徑就是一些小方格的組合。當然是可以劃分成任意形狀,甚至是精確到每一個畫素點,這完全取決於你的遊戲的需求。一般情況下劃分成方格就可以滿足我們的需求,同時也便於計算。
如下圖區域,被簡化成6*6的小方格。其中綠色表示起點,紅色表示終點,黑色表示路障,不能通行。
概述演算法步驟
先描述A*演算法的大致過程:
-
將初始節點放入到open列表中。
-
判斷open列表。如果為空,則搜尋失敗。如果open列表中存在目標節點,則搜尋成功。
-
從open列表中取出F值最小的節點作為當前節點,並將其加入到close列表中。
-
計算當前節點的相鄰的所有可到達節點,生成一組子節點。對於每一個子節點:
如果該節點在close列表中,則丟棄它 如果該節點在open列表中, 則檢查其通過當前節點計算得到的F值是否更小, 如果更小則更新其F值,並將其父節點設定為當前節點。 如果該節點不在open列表中, 則將其加入到open列表,並計算F值,設定其父節點為當前節點。
-
轉到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
相關文章
- 尋路演算法之A*演算法詳解演算法
- 詳細解讀!推薦演算法架構——召回演算法架構
- 手寫 Promise 詳細解讀Promise
- Dockerfile ,ADD詳細解讀Docker
- Oracle SCN機制詳細解讀Oracle
- 矩陣分解--超詳細解讀矩陣
- Oracle AWR報告詳細解讀Oracle
- LINUX top命令詳細解讀Linux
- PHP陣列的詳細解讀PHP陣列
- JavaScript 的 Date 最詳細解讀JavaScript
- WebUploader API 文件詳細解讀WebAPI
- PHP中return用法詳細解讀PHP
- Redis 主從複製詳細解讀Redis
- 詳細解讀阿里手冊之MySQL阿里MySql
- 詳細解讀go語言中的chnanelGoNaN
- Android BLE藍芽詳細解讀Android藍芽
- 最詳細的JavaScript和事件解讀JavaScript事件
- Android事件機制詳細解讀Android事件
- 手寫 call apply bind 詳細解讀APP
- Java面試-List中的sort詳細解讀Java面試
- C++指標的概念解讀 超詳細C++指標
- 詳細解讀微服務的兩種模式微服務模式
- Android的.so檔案詳細解讀Android
- 尋路之 A* 搜尋演算法演算法
- 一種高效的尋路演算法 - B*尋路演算法演算法
- 全網最!詳!細!Tarjan演算法講解。演算法
- 生命週期詳細解讀(含部分原始碼)原始碼
- 詳細解讀Service Mesh的資料面Envoy
- 老馬的春天:SDWebImage原始碼詳細解讀系列Web原始碼
- Linux 系統配置檔案詳細解讀Linux
- 詳細解讀Python中的__init__()方法Python
- A*演算法(超級詳細講解,附有舉例的詳細手寫步驟)演算法
- Tarjan演算法及其應用 總結+詳細講解+詳細程式碼註釋演算法
- 網路安全Bypass網路卡詳細講解
- MyCat分片:分片規則的十四種演算法詳細解讀&程式碼實現(上篇)演算法
- 【UGUI原始碼分析】Unity遮罩之Mask詳細解讀UGUI原始碼Unity遮罩
- C++中的指標與引用詳細解讀C++指標
- 演算法修養--A*尋路演算法演算法