A*尋路算法

若是你是一個遊戲開發者,或者開發過一些關於人工智能的遊戲,你必定知道A*算法,若是沒有接觸過此類的東東,那麼看了這一篇文章,你會對A*算法從不知道變得了解,從瞭解變得理解。
我不是一個純粹的遊戲開發者,我只是由於喜歡而研究,由於興趣而開發,從一些很小的遊戲開始,直到接觸到了尋路等人工智能,纔開始查找一些關於尋路方面的文章,從而知道了A*算法,由於對於初期瞭解的我這個算法比較複雜,開始只是copy而已,如今咱們一塊兒來精密的研究一下A*算法,以及提升它的速度的方法。算法

一,A*算法原理app

我看過Panic翻譯的國外高手Patrick Lester的一篇關於A*算法初探的文章,如今我就根據回憶,來慢慢認識A*算法的原理。
咱們先來看一張圖ui

aa

圖中從起點到終點,須要繞過一些遮擋,也許咱們看的比較簡單,可是實際上,交給電腦來實現卻要通過一番周折,電腦如何知道哪裏有遮擋物,又是如何找到從起點到終點的最短路徑的呢?
瞭解這些,咱們首先要知道一個公式:
F = G + H 
其中,F 是從起點通過該點到終點的總路程,G 爲起點到該點的「已走路程」,H 爲該點到終點的「預計路程」。
A*算法,要從起點開始,按照它的算法,逐步查找,直到找到終點。
初期,地圖上的節點都是未開啓也未關閉的初始狀態,咱們每檢測一個節點,就要開啓一些節點,檢測完以後,要把檢測完的節點,就要把它關閉。
咱們須要一個開啓列表和關閉列表,用來儲存已經被開啓的節點和被關閉的節點。
這些就讓咱們在實際過程當中來深刻了解吧。
看下面的圖this

aa

首先,咱們來從起點出發,開啓它周圍的全部點,由於遮擋是沒法經過的,咱們不去管它,這樣,被咱們開啓的節點,就是圖中的三個節點,它們的父節點就是起點,因此圖中的箭頭指向起點,計算相應的FGH值,如圖所視,檢測完畢,將起點放入關閉列表。
這個時候,咱們從被開啓的全部節點中查找F值最小的節點,作爲下一次檢測的節點,而後開啓它周圍的點。
這時候起點左方和下方的F值都是70,咱們根據本身的喜愛選擇任意一個,這裏先選擇下方的節點進行檢測。
以下圖人工智能

aa

首先把未被開啓的剩下的節點的父節點指向檢測點。
已經開啓的點,咱們不去開啓第二遍,可是咱們計算一下從檢測點到達它們的新的G值是否更小,若是更小則表明目前的路徑是最優的路徑,那麼把這個節點的父節點改成目前的檢測點,並從新計算這個點的FGH的值,所有檢測完畢以後,關閉檢測點,而後開始尋找下一個節點,如此循環,直到找到終點。
而後從終點開始,按照每一個節點的父節點,倒着畫出路徑,以下圖spa

 

1

 

這個就是A*算法的原理,說難卻是不難,可是對於初步接觸的人來講有點費勁而已。.net

 

二,A*算法的速度翻譯

前面,咱們瞭解了A*算法的原理,發現,在每次查找最小節點的時候,咱們須要在開啓列表中查找F值最小的節點,研究A*的速度,這裏就是關鍵,如何更快的找出這個最小節點呢?blog

1,普通查找算法排序

咱們先來看看,最簡單的作法,就是每次都把開啓列表中全部節點檢測一遍,從而找到最小節點

[c-sharp] view plaincopy

  1. private function getMin():uint {  

  2.     var len:uint = _open.length;  

  3.     var min:Object = new Object();  

  4.     min.value_f = 100000;  

  5.     min.i = 0;  

  6.     for (var i:uint = 0; i<len; i++) {  

  7.         if (min.value_f>_open[i].value_f) {  

  8.             min.value_f = _open[i].value_f;  

  9.             min.i = i;  

  10.         }  

  11.     }  

  12.     return min.i;  

  13. }  

這裏我用了一張很簡單的地圖來驗證此方法
運行結果如圖

aa

咱們看到,耗時38毫秒,其實這個數字是不許確的,咱們權且看成參考

2,排序查找算法

顧名思義,這個算法就是,始終維持開啓列表的排序,從小到大,或者從大到小,這樣當咱們查找最小值時,只須要把第一個節點取出來就好了
維持列表的排序,方法是在太多了,個人方法也許很笨,勉強參考一下吧,咱們每次排序的同時,順便計算列表中的平均值,這樣插入新節點的時候,根據這個平均值來判斷從前面開始判斷仍是從後面開始判斷

[c-sharp] view plaincopy

  1. //加入開放列表  

  2. private function setOpen(newNode:Object):void {  

  3.     newNode.open = true;  

  4.     var __len:int = _open.length;  

  5.     if (__len==0) {  

  6.         _open.push(newNode);  

  7.         _aveOpen = newNode.value_f;  

  8.     }else {  

  9.         //和F平均值作比較,決定從前面或者後面開始判斷  

  10.         if (newNode.value_f<=_aveOpen) {  

  11.             for (var i:int=0; i<__len; i++) {  

  12.                 //找到比F值小的值,就插在此值以前  

  13.                 if (newNode.value_f<=_open[i].value_f) {  

  14.                     _open.splice(i, 0, newNode);  

  15.                     break;  

  16.                 }  

  17.             }  

  18.         } else {  

  19.             for (var j:int=__len; j>0; j--) {  

  20.                 //找到比F值大的值,就插在此值以前  

  21.                 if (newNode.value_f>=_open[(j-1)].value_f) {  

  22.                     _open.splice(j, 0, newNode);  

  23.                     break;  

  24.                 }  

  25.             }  

  26.         }  

  27.         //計算開放列表中F平均值  

  28.         _aveOpen += (newNode.value_f-_aveOpen)/_open.length;  

  29.     }  

  30. }  

  31. //取開放列表裏的最小值  

  32. private function getOpen():Object {  

  33.     var __next:Object =  _open.splice(0,1)[0];  

  34.     //計算開放列表中F平均值  

  35.         _aveOpen += (_aveOpen-__next.value_f)/_open.length;  

  36.         return __next;  

  37. }  

運行結果如圖

aa

咱們看到,耗時25毫秒,這個數字雖然不許確的,可是與普通查找算法相比較,速度確實是提升了

3,二叉樹查找算法
(參考了火夜風舞的C++新霖花園中的文章)
這個算法能夠說是A*算法的黃金搭檔,也是被稱爲苛求速度的binary heap」的方法
就是根據二叉樹原理,來維持開啓列表的「排序」,這裏說的排序只是遵循二叉樹的原理的排序而已,即父節點永遠比子節點小,就像下面這樣
   1
|    |
5    9
|   |  |
7  12 10
二叉樹每一個節點的父節點下標 = n / 2;(小數去掉)
二叉樹每一個節點的左子節點下標 = n * 2;右子節點下標 = n * 2 +1
注意,這裏的下標和它的值是兩個概念

[c-sharp] view plaincopy

  1. //加入開放列表  

  2. private function setOpen(newNode:Object,newFlg:Boolean = false):void {  

  3.     var new_index:int;  

  4.     if(newFlg){  

  5.         newNode.open = true;  

  6.         var new_f:int = newNode.value_f;  

  7.         _open.push(newNode);  

  8.         new_index = _open.length - 1;  

  9.     }else{  

  10.         new_index = newNode.index;  

  11.     }  

  12.     while(true){  

  13.         //找到父節點  

  14.         var f_note_index:int = new_index/2;  

  15.         if(f_note_index > 0){  

  16.             //若是父節點的F值較大,則與父節點交換  

  17.             if(_open[new_index].value_f < _open[f_note_index].value_f){  

  18.                 var obj_note:Object = _open[f_note_index];  

  19.                 _open[f_note_index] = _open[new_index];  

  20.                 _open[new_index] = obj_note;  

  21.                                   

  22.                 _open[f_note_index].index = f_note_index;  

  23.                 _open[new_index].index = new_index;  

  24.                 new_index = f_note_index;  

  25.             }else{  

  26.                 break;  

  27.             }  

  28.         }else{  

  29.             break;  

  30.         }  

  31.     }  

  32. }  

  33. //取開放列表裏的最小值  

  34. private function getOpen():Object {  

  35.     if(_open.length <= 1){  

  36.         return null;  

  37.     }  

  38.     var change_note:Object;  

  39.     //將第一個節點,即F值最小的節點取出,最後返回  

  40.     var obj_note:Object = _open[1];  

  41.     _open[1] = _open[_open.length - 1];  

  42.     _open.pop();  

  43.     _open[1].index = 1;  

  44.     var this_index:int = 1;  

  45.     while(true){  

  46.         var left_index:int = this_index * 2;  

  47.         var right_index:int = this_index * 2 + 1;  

  48.         if(left_index >= _open.length){  

  49.             break;  

  50.         }else if(left_index == _open.length - 1){  

  51.             //當二叉樹只存在左節點時,比較左節點和父節點的F值,若父節點較大,則交換  

  52.             if(_open[this_index].value_f > _open[left_index].value_f){  

  53.                 change_note = _open[left_index];  

  54.                 _open[left_index] = _open[this_index];  

  55.                 _open[this_index] = change_note;  

  56.                                   

  57.                 _open[left_index].index = left_index;  

  58.                 _open[this_index].index = this_index;  

  59.                                   

  60.                 this_index = left_index;  

  61.             }else{  

  62.                 break;  

  63.             }  

  64.         }else if(right_index < _open.length){  

  65.             //找到左節點和右節點中的較小者  

  66.             if(_open[left_index].value_f <= _open[right_index].value_f){  

  67.                 //比較左節點和父節點的F值,若父節點較大,則交換  

  68.                 if(_open[this_index].value_f > _open[left_index].value_f){  

  69.                     change_note = _open[left_index];  

  70.                     _open[left_index] = _open[this_index];  

  71.                     _open[this_index] = change_note;  

  72.                                       

  73.                     _open[left_index].index = left_index;  

  74.                     _open[this_index].index = this_index;  

  75.                                       

  76.                     this_index = left_index;  

  77.                 }else{  

  78.                     break;  

  79.                 }  

  80.             }else{  

  81.                 //比較右節點和父節點的F值,若父節點較大,則交換  

  82.                 if(_open[this_index].value_f > _open[right_index].value_f){  

  83.                     change_note = _open[right_index];  

  84.                     _open[right_index] = _open[this_index];  

  85.                     _open[this_index] = change_note;  

  86.                                       

  87.                     _open[right_index].index = right_index;  

  88.                     _open[this_index].index = this_index;  

  89.                                       

  90.                     this_index = right_index;  

  91.                 }else{  

  92.                     break;  

  93.                 }  

  94.             }  

  95.         }  

  96.     }  

  97.     return obj_note;  

  98. }  

運行結果如圖

aa

咱們看到,耗時15毫秒,速度是這三個方法裏最快的,可是由於這個數字是不夠準確的,實際上,用二叉樹查找法,會讓A*算法的速度提升幾倍到10幾倍,在一些足夠複雜的地圖裏,這個速度是成指數成長的。

4,總結得出結論,用了A*算法,就要配套的用它的黃金搭檔,二叉樹,它可讓你的遊戲由完美走向更完美。

相關文章
相關標籤/搜索