工作中需要優化A*算法,研究了一天,最后取得了不錯的效果。看網上的朋友還沒有相關的研究,特此記錄一下。有錯誤歡迎大家批評指正。如需轉載請注明出處,http://www.cnblogs.com/Leonhard-/p/6842052.html,這是對作者最起碼的尊重,謝謝大家。
本文結構如下:
一、A*算法優化背景介紹
二、A*算法介紹與實現簡述
三、深入思考優化需求
1.啟發函數的設計思路
2.啟發函數與cost值的相對關系
3.啟發函數中對k值大小的深入思考
四、總結
一、A*算法優化背景介紹
A*算法運用的場景很廣泛,不同的運用場景有不同的A*設計思路,本文不是描述所有環境下的設計思路,而僅是記錄工作中碰到的A*算法優化思路。使用的背景是在一個二維地圖上,角色允許從周圍的八個方向進行移動(如下圖,角色在粉紅色位置,周圍藍色位置為可走路線),地圖中帶有障礙物,求從某一個點到另外一個點的靜態路徑。具體的要求:1.讓這個路徑盡可能的短;2.讓移動更加平滑,盡可能少的出現角色在選擇方向上的抖動(后文有進一步介紹)。
二、A*算法介紹與實現簡述
1.A*算法簡單介紹
網上已經有朋友發了短小精干卻內容清晰的A*算法教程,沒有研究過A*算法的朋友可以先到這個鏈接學習基本A*算法(如果想進一步了解A*算法的種種相關信息,可以查看原版鏈接或者翻譯版鏈接)。此處則不再對重復的信息進行復制粘貼。
2.工作當中的A*算法實現思路。
大概思路是這樣的,代碼用的是一個heap來存儲open隊列節點,用一個哈希表來存儲close隊列(當然也可以使用其他的方法,上述給的鏈接里有講,本文就不再贅述)。
三、深入思考優化需求
1.估值函數的設計思路
其中估值函數如下:
1 int estimateValue(Point gaol,Point current) 2 { 3 int dx = abs(goal.x-current.x); 4 int dy = abs(goal.y-current.y); 5 if(dx > dy) 6 return 10*dx+4*dy; 7 else 8 return 10*dy+4*dx; 9 }
以dx>dy的情況為例,其中的10*dx+4*dy是什么意思呢?如下圖,粉紅色為需要考慮估值的點,棕色為終點,其中棕色線條的長度*10則為估值函數的估計值(放大10倍是為了去掉小數點),即10*(dx+0.4*dy)。
2.估值函數與cost值的相對關系
為了說明角色往不同方向移動的cost值,我們給圖的格子標上數字,方便說明,如下圖。公司代碼中設定,往2,4,5,7方向移動,cost值為1,往1,3,6,8位置移動,則cost值為2。細心的朋友會發現,這個地方有了錯誤。上述我們為了將1位小數的浮點數轉換為整數,把estimate值乘了個系數10,此處也應該乘以10。即往2,4,5,7方向移動,cost值為10,往1,3,6,8位置移動,則cost值為20。根據以往的習慣,1.當目的地在位置3時,我們鼓勵玩家直接從粉紅點跳到3;2.當目的地點在位置5右邊一個格子時,我們鼓勵玩家先到5,然后再移動到5右邊的格子。如果往斜方向上移動的取值為20的話,我們發現會出現一個問題,就是從粉紅點直接到3的cost等於先到5再到3的cost值,這樣會引起移動時角色的“抖動”(角色經常在面向3和面向5的兩個方向中切換,看起來就像在抖動);如果我們把往斜方向上移動的取值為10的話,這樣斜方向上和水平方向上又有了新問題,因為從粉紅色格子到3再到5右邊的格子的cost值等於粉紅色格子先到5再到5右邊格子的cost值,這樣看起來比較奇怪,當地圖比較大時,會更明顯,當角色要從地圖左下角到右下角時,角色先到地圖中間的最上方,然后又往右下角進行前進,而且會增加角色的“抖動”。那么往斜方向上移動的cost值的取值范圍最好是屬於(10,20),這里假設我們取14,后面介紹這會出現的問題。
為什么會出現角色抖動呢,分析以上我們可以知道,當estimate+cost值相等的時候,維護open隊列的heap只能隨機取一個,所以不能保證取出來的就一定是按我們想要的某一個方向前進的節點。那解決思路是什么呢,就是estimate+cost值盡量少的出現相等的情況,要么就是修改heap讀取的方式,一目了然,前者才是個好方法。而上面的cost取值為14,經過計算我們發現,往右上方移動與往右邊移動的estimate+cost的值相等,也會出現“抖動”。而此時我們將往斜上方移動的cost值取(10,14)或者(14,20),問題圓滿解決了。那么往斜上方移動的cost值取(10,14)或者(14,20)有什么區別呢?看圖來說明。
當我們取(10,14)時,尋路結果如下:
當我們取(14,20)的話,尋路結果如下:
結合上面的兩幅圖,我們清楚了cost值和dy系數(后文稱為k)之間的關系(只考慮dx>dy的情況,dy>dx情況類似)。當斜方向與水平方向的cost的插值大於k時,尋路會優先往x方向走,一直走到dx == dy時候開始走斜線,反之則先走斜線,后走水平線。
3.估值函數中的k值的深入思考
上一小節我們討論了估值函數中的k值和往斜上方走的cost值的相對關系,那么我們進一步思考,這個k值是否可以隨便取呢。答案:是,但也不全是。為什么這么說呢?具體的k值對性能和速度有很大影響,如果k的選取讓estimate<=從該點到目標節點的實際cost值,那么它能取到最優解,但是也因為estimate+cost的值較小的原因,heap考慮的節點數變多,讓算法的速度減慢;反之,如果k的選取讓estimate>從該點到目標節點的實際cost值,那么它不一定取到最優解,此時A*算法便不再是A*算法,淪為了Greedy first-search algorithm,因為estimate+cost的值較大的原因,heap考慮的節點數變少,讓算法的速度加快,但是也不能保證最優解了。所以,為了取到最優解,k的值不能大於4。
四、總結
本文介紹了二維地圖上A*算法的估值函數的設計思路並討論了它與cost值的相對關系及其影響。綜上所述,二維地圖上為了保證讓角色走最短路徑,且讓角色盡量不出現“抖動”,那么估值函數設計為 G = 10*dx + 4*dy就比較好。而且考慮到項目組的游戲地圖為矩形如下圖(黃色),建築物的俯視圖如下圖(黑色),先往水平方向走比先往斜方向走好看,所以往斜上方走的cost值取(14,20)比較好。