1. 程式人生 > >淺談遊戲自動尋路A*演算法

淺談遊戲自動尋路A*演算法

 尋路是遊戲中非常重要的一個元素,如何找到一條最短的路徑是程式需要設計的演算法,現在最為流行的尋路演算法是A*演算法。A*演算法與狀態空間搜尋結合的相當緊密。

    狀態空間搜尋,就是將問題求解的過程表現為從初始狀態到目標狀態尋找這個路徑的過程,通俗的說就是在解一個問題的時候找到一條解題過程可以從求解的開始到問題的結束。

    由於求解過程中求解條件的不確定與不完備性使得問題的求解過沖中的分支有很多,這就產生了多條求解的路徑,這些路徑過程一個圖這個圖就是狀態空間。問題的求解時機上就是在這個圖中找個一個路徑可以從開始到結束,這個過程就是狀態空間搜尋。

    常用的狀態空間搜尋有深度優先和廣度優先,廣度優先是從初始狀態一層一層的向下找,知道找到結果目標為止,深度優先是按照一定的順序先查詢完一個分支再查詢另一個分支,知道找到目標結果為止。這兩種搜尋方法有的很大缺陷是它們都是在一個給定的狀態空間中窮舉。這在狀態空間不大的情況下是很適合的演算法,但是當空間很大並且不可預測的情況下就不可取。這個時候這兩種演算法的效率太低甚至有時是無法完成,所以要用到另一種演算法---啟發式搜尋。

    啟發式搜尋就是在狀態空間中對每一個搜尋為止進行評估,指導找到最好的為止,再從這個位置進行搜尋直到目標位置為止。在啟發式搜尋中對為止的評估是十分重要的,採用不同的估價可能有不同的結果。

   啟發式搜尋中的估價函式表示為:

   f(n)=g(n)+h(n)

   其中f(n)是節點n的估價函式,g(n)是在狀態空間中從初始點到n節點的實際代價,h(n)是從n節點到目標節點最佳路徑的估價代價。這個裡主要是h(n)體現了搜尋的啟發資訊,因為g(n)是己知的。換個說法就是g(n)代表了索索的廣度優先趨勢但是當h(n)>>g(n)時,可以省略g(n),從而提高效率。

   啟發式搜尋其實也有很多演算法,比如區域性擇優搜尋,最好優先搜尋等。A*也是如此,這些演算法都啟用了啟發函式,但在具體的選取最佳搜尋節點時的策略不同。比如區域性擇優演算法就是在搜尋的過程中選取了最佳節點候捨棄了其他的兄弟節點,父親節點並且一直搜尋下去。這種搜尋結果很明顯,由於捨棄了其他的節點因此可能也把最佳的節點捨去偶爾。最好優先就聰明一點搜尋的時候並沒有捨去節點,除非該節點是死節點。在沒一步的估價中都吧當前的節點和以前的節點的估價值進行比較從而得到最佳節點,這樣防止了最佳節點的丟失。

   A*演算法也是一種最好優先的演算法,只是加上了一些特定的約束條件,由於在一些問題求解時,希望能夠求解出狀態空間搜尋的最短路徑也就是用最快的方法求解出問題,A*演算法的目的就是這樣。其估價的函式可以表示為:

  f'(n)=g'(n)+h'(n)

   這裡的f'(n)是估價函式,g'(n)是起點到終點的最短路徑值,h'(n)是n到目標的最短路徑的啟發值。由於f'(n)是無法提前預先知道的,因此用前面的估價函式f(n)做近似g(n)代表g'(n),但是g(n)≥g'(n)才可以通常都是大於所以不要考慮,但是h(n)代替h'(n)時候需要h(n)≤h'(n)才可以。可以證明應用這樣的評估函式是可以找到最短路徑的,因此應用這種評估函式的最好的優先演算法就是A*演算法。

   至於h(n)的啟發函式的資訊性,就是在估計一個節點值的約束條件,如果資訊越多或者約束條件越多則排除節點就越多,估價函式就越好或者說這個演算法就越好。這就是為什麼廣度優先演算法很不好的原因,因為起h(n)=0一點啟發資訊都沒有,但是在遊戲的開發中由於實時性的要求,h(n)的實質資訊越多計算量也大消耗的時間就長,其次在犧牲演算法準確性的前提下就可以適當的減少h(n)的啟發資訊。

我們先看下最好優先演算法的邏輯(起始為止為A結束位置是P,字母后數字為節點的估價值):

    搜尋的過程中設定兩個表:OPEN和CLOSE。OPEN表儲存了所有已生成的未考察的節點。CLOSE表中記錄了已訪問的節點。演算法中有一步是根據估價函式重新排列OPEN表,這樣迴圈中的每一步值考慮OPEN中狀態最好的節點搜尋過程如下:

 1.初始狀態

   OPEN = [A5];CLOSED=[ ] ;

 2.估算A5,取得所有子節點,並放入OPEN表中

   OPEN = [B4,C4,D6];CLOSED = [A5];

 3.估算B4,取得所有子節點,並放入OPEN表中

   OPEN = [C4,E5,F5,D6];CLOSED = [B4,A5];

 4.估算C4,取得所有子節點,並放入OPEN表中

   OPEN = [H3,G4,E5,F5,D6];CLOSED = [C4,B4,A5];

 5.估算H3,取得所有子節點,並放入OPEN表中

   OPEN = [O2,P3,G4,E5,F5,D6];CLOSED = [H3,C4,B4,A5];

 6.估算O2,取得所有子節點,並放入OPEN表中

   OPEN = [P3,G4,E5,F5,D6];CLOSED = [O2,H3,C4,B4,A5];

 7.估算P3得到解

虛擬碼如下:

 Best_First_Seach()

{

  Open = [起始節點];

  Closed = [ ];

  while(Open表非空)

  {

    從Open中取得一個節點X,並從Open表中刪除。

    if(X節點是目標節點)

    {

      求的路徑PATH;

      return PATH;

    }

    for(每個X的子節點Y)

    {

      if(Y不在OPEN表和CLOSE表中)

      {

       求Y的估價值;

       將Y插入OPEN表中;

      }

      else if(Y在OPEN表中)

      {

        if(Y的估價值小於OPEN表的估價值)

        更新OPEN表中的估價值;

      }

      else

      {

      if(Y的估價值小於CLOSE表的估價值)

      更新CLOSE表中的股價值

      從CLOSE表中移出節點,放入OPEN表中;

      }

    }

    講X節點插入CLOSE表中;

    按照估價值講OPEN表中的節點排序;

  }

}