原理
AStar 使用 F = G + H 來(lái)評(píng)估一個(gè)節(jié)點(diǎn)。
其中 G 代表起始節(jié)點(diǎn)到這個(gè)節(jié)點(diǎn)的代價(jià),H 代表目的節(jié)點(diǎn)到這個(gè)節(jié)點(diǎn)的代價(jià)。這樣,從起始節(jié)點(diǎn)開(kāi)始,不斷的尋找鄰居節(jié)點(diǎn)中 F 最小的,直到檢測(cè)到目的節(jié)點(diǎn)從而找到路徑為止。
下面是算法流程圖:
- AStar 維護(hù)著一個(gè)開(kāi)放列表和一個(gè)封閉列表。開(kāi)放列表中存放著待檢查 F 值的節(jié)點(diǎn),每次主循環(huán)中都從 openList 中尋找 F 值最小的節(jié)點(diǎn)作為當(dāng)前節(jié)點(diǎn),然后將當(dāng)前節(jié)點(diǎn)的鄰居節(jié)點(diǎn)加入到 openList 中作為待檢查節(jié)點(diǎn)。封閉列表存放著不再檢查的節(jié)點(diǎn),每當(dāng)選出一個(gè)當(dāng)前節(jié)點(diǎn)之后意味著它的鄰居節(jié)點(diǎn)將要或已經(jīng)加入到開(kāi)放列表中,這個(gè)當(dāng)前節(jié)點(diǎn)便成為了一個(gè)不再檢查的節(jié)點(diǎn),所以要把它加入到 closeList 中,防止再次檢查。
- 在鄰居節(jié)點(diǎn)循環(huán)中如果這個(gè)鄰居節(jié)點(diǎn)不在 openList 中,那就需要設(shè)置 F、G、H 和 parentNode 并加入到 openList 中備選。
- 在鄰居節(jié)點(diǎn)循環(huán)中如果這個(gè)節(jié)點(diǎn)已經(jīng)在 openList 中,說(shuō)明它之前已經(jīng)作為鄰居節(jié)點(diǎn)加入到了 openList 中,但是沒(méi)有被選為當(dāng)前節(jié)點(diǎn)(因?yàn)樗?F 不是最小的)。而此時(shí)它又成為了另一個(gè)節(jié)點(diǎn)的鄰居節(jié)點(diǎn),如果經(jīng)由當(dāng)前節(jié)點(diǎn)到這個(gè)節(jié)點(diǎn)的 F 值變小了那就更新這個(gè)鄰居節(jié)點(diǎn)的數(shù)據(jù),否則什么也不做。
- openList 為空說(shuō)明搜索了所有地圖而沒(méi)有找到路徑。
- 當(dāng)前節(jié)點(diǎn)為目的節(jié)點(diǎn)說(shuō)明路徑已經(jīng)找到,沿著當(dāng)前節(jié)點(diǎn)的 parentNode 回溯就可以得到路徑數(shù)據(jù)。
G 和 H
AStar 依據(jù) G 和 H 值來(lái)評(píng)估一個(gè)節(jié)點(diǎn)在本次尋路過(guò)程中的代價(jià)。
這兩個(gè)值將經(jīng)由這個(gè)節(jié)點(diǎn)的路徑的代價(jià)分割成兩部分:
- 一部分是由起始節(jié)點(diǎn)到這個(gè)節(jié)點(diǎn)的代價(jià) G ,因?yàn)槁窂降乃阉鬟^(guò)程是從起始節(jié)點(diǎn)開(kāi)始循環(huán)的檢查當(dāng)前節(jié)點(diǎn)的每個(gè)鄰居節(jié)點(diǎn),所以這個(gè)值是確定的。
- 另一部分 F 是這個(gè)節(jié)點(diǎn)到目的節(jié)點(diǎn)的代價(jià),這個(gè)值一般是一個(gè)估計(jì)值(也可以是精確的)。這樣,因?yàn)橛?H 值影響著 F 的大小,路徑的搜索會(huì)有一個(gè)大概的方向,即 H 值會(huì)不斷的把搜索方向?qū)蚰康墓?jié)點(diǎn)的方向,這也是為什么 AStar 會(huì)更快,因?yàn)?H 值減少了算法檢查的節(jié)點(diǎn)個(gè)數(shù)。
下面用幾張截圖來(lái)說(shuō)明這兩個(gè)值的作用。
如下圖,現(xiàn)在有這樣一個(gè)地圖,先不考慮地圖中有其它山啊水啊不可達(dá)的區(qū)域,假設(shè)地圖上任何一個(gè)節(jié)點(diǎn)都是可達(dá)的,現(xiàn)在要在兩個(gè)圓點(diǎn)標(biāo)記的節(jié)點(diǎn)中尋找一條路徑。
首先是使用了“曼哈頓”方法計(jì)算 H 值的 AStar 結(jié)果,圖中藍(lán)色的節(jié)點(diǎn)是算法過(guò)程中被加入到了 openList 中的節(jié)點(diǎn),可以看到這個(gè)搜索過(guò)程沒(méi)有檢查過(guò)多的節(jié)點(diǎn),從一開(kāi)始就向著目的節(jié)點(diǎn)的方向搜索過(guò)去。
下面,把每個(gè) H 值的節(jié)點(diǎn)都置 0,也就是說(shuō)完全消除 H 值對(duì)算法的影響,根據(jù)原理大概可以預(yù)測(cè)到搜索過(guò)程會(huì)以起始節(jié)點(diǎn)為圓心,不斷的向外擴(kuò)展,直到到達(dá)了目的節(jié)點(diǎn)為止。下面是截圖:
G 讓算法找到更好的路徑,而 H 讓算法更快的找到路徑。
G 和 H 的單位問(wèn)題
因?yàn)?F 是由 G 和 H 相加得來(lái)的,所以二者的度量應(yīng)該是統(tǒng)一的。如果二者的度量不統(tǒng)一就會(huì)使二者中的一個(gè)值在節(jié)點(diǎn)評(píng)估中起主導(dǎo)作用,而另一個(gè)值變得失效了。比如再看使用了“曼哈頓”方法計(jì)算 H 值的 AStar ,這次把 H 值縮小十倍,算法檢查了更多的節(jié)點(diǎn),從而速度會(huì)變慢。截圖:
計(jì)算 H 的幾種常用方法
精確的 H 值
可以在路徑搜索之前預(yù)先計(jì)算任意兩個(gè)節(jié)點(diǎn)之間的代價(jià),然后在計(jì)算 H 值的時(shí)候直接查找這個(gè)值。但是這個(gè)方法在實(shí)際中不太現(xiàn)實(shí),因?yàn)樵诖蠖鄶?shù)游戲中一般地圖比較大、節(jié)點(diǎn)多(預(yù)計(jì)算的數(shù)據(jù)會(huì)很多)、存在著未知區(qū)域(無(wú)法預(yù)先計(jì)算)、地圖上還存在著其它的移動(dòng)目標(biāo)。這些因素都會(huì)使得這種精確 H 值的方法不是那么容易實(shí)現(xiàn)。
近似精確的 H 值
在地圖上均勻的設(shè)置一些導(dǎo)航點(diǎn),預(yù)先計(jì)算任意兩個(gè)導(dǎo)航點(diǎn)的代價(jià)。尋路過(guò)程中,當(dāng)要計(jì)算 H 值的時(shí)候,先尋找距離當(dāng)前節(jié)點(diǎn)最近的導(dǎo)航點(diǎn) w1 和距離目的節(jié)點(diǎn)最近的導(dǎo)航點(diǎn) w2 ,這樣 H 值就變成了一下這種形式:
H = h(當(dāng)前節(jié)點(diǎn), w1) + h(目的節(jié)點(diǎn), w2) + distance(w1, w2)
曼哈頓方法
設(shè)當(dāng)前節(jié)點(diǎn): n(column, row) ,目的節(jié)點(diǎn) t(column, row)
HManhattan(n) = D * [abs(n.column - t.column) + abs(n.row - t.row)]
(其中 D 是為了平衡 G 和 H 的度量)
對(duì)角線(xiàn)方法
設(shè)當(dāng)前節(jié)點(diǎn): n(column, row) ,目的節(jié)點(diǎn) t(column, row)
如上圖,路徑可以先沿對(duì)角線(xiàn)方向走向中間節(jié)點(diǎn) x,再沿直線(xiàn)走向目的節(jié)點(diǎn) t。
首先,對(duì)角線(xiàn)的代價(jià)為
Hd = Dd * [min(abs(n.column - t.column), abs(n.row - c.row))]
直線(xiàn)的代價(jià)為:
Hs = Ds * [max(abs(n.column - t.column), abs(n.row - c.row)) - min(abs(n.column - t.column), abs(n.row - c.row))]
H = Hd + Hs
歐幾里得和平方歐幾里德方法
設(shè)當(dāng)前節(jié)點(diǎn): n(column, row) ,目的節(jié)點(diǎn) t(column, row)
H = D * sqrt((n.column - t.column)(n.column - t.column) + (n.row - t.row)(n.row - t.row))
計(jì)算機(jī)計(jì)算平方根比較耗時(shí)間,考慮把開(kāi)平方的過(guò)程去掉,但是這樣會(huì)導(dǎo)致 G 和 H 的度量不同。
以上就是 AStar 的一些基本概念,最后來(lái)一個(gè)思維導(dǎo)圖吧:
本文主要的代碼和資源文件在這里可以找到:https://github.com/Jiajiaju/AStar