超級基礎A*尋路教程

 學習了一下A*算法,但是天生對算法無奈 ,還好一不小心找到下面這篇文章。如果你苦於無法理解網上各大牛人的巔峯討論以及他們火星文般的源代碼,那麼這篇文章實在是太適合你不過了~   快來看吧,我也正在看,大家一起路過,學習下~~

來源於: GameDev.net
作 者: Patrick Lester [
[email protected]]
翻 譯: 孫璨 [
[email protected]]


雖然A*(讀作A星)算法對初學者來說是比較深奧難懂,但是一旦你找到門路了,它又會變得非常簡單。網上有很多解釋A*算法的文章,但是大多數是寫給那些有一定基礎的人看的,而您看到的這一篇呢,是真正寫給菜鳥的。

本篇文章並不想給這個算法題目作一些權威性論斷,而是闡述它的基本原理,併爲你理解更多相關資料與討論打下基礎。文章末尾給出了一些比較好的鏈接,放在“進階閱讀”一節之後。

最後,本文不是編程規範,你將可能使這裏講述的東西編寫成任何計算機語言。在本文的末尾我還給出了一個例子程序包的下載鏈接,也許正合你意。在這個包中有C++和Blitz Basic兩個版本的程序代碼,如果你只是想看看A*算法是如何運作的,該包中也有可直接執行的文件供你研究。

我們還是要超越自己的(把算法弄懂),所以,讓我們從頭開始吧!
 
初步:搜索區域

我們假設某個人要從A點到達B點,而一堵牆把這兩個點隔開了,如下圖所示,綠色部分代表起點A,紅色部分代表終點B,藍色方塊部分代表之間的牆。



[圖一]
你首先會注意到我們把這一塊搜索區域分成了一個一個的方格,如此這般,使搜索區域簡單化,正是尋找路徑的第一步。這種方法將我們的搜索區域簡化成了一個普通的二維數組。數組中的每一個元素表示對應的一個方格,該方格的狀態被標記爲可通過的和不可通過的。通過找出從A點到B點所經過的方格,就能得到AB之間的路徑。當路徑找出來以後,這個人就可以從一個格子中央移動到另一個格子中央,直到抵達目的地。
這些格子的中點叫做節點。當你在其他地方看到有關尋找路徑的東西時,你會經常發現人們在討論節點。爲什麼不直接把它們稱作方格呢?因爲你不一定要把你的搜索區域分隔成方塊,矩形、六邊形或者其他任何形狀都可以。況且節點還有可能位於這些形狀內的任何一處呢?在中間、靠着邊,或者什麼的。我們就用這種設定,因爲畢竟這是最簡單的情況。

開始搜索

當我們把搜索區域簡化成一些很容易操作的節點後,下一步就要構造一個搜索來尋找最短路徑。在A*算法中,我們從A點開始,依次檢查它的相鄰節點,然後照此繼續並向外擴展直到找到目的地。
我們通過以下方法來開始搜索:
1.
從A點開始,將A點加入一個專門存放待檢驗的方格的“開放列表”中。這個開放列表有點像一張購物清單。當前這個列表中只有一個元素,但一會兒將會有更多。列表中包含的方格可能會是你要途經的方格,也可能不是。總之,這是一個包含待檢驗方格的列表。

2.
檢查起點A相鄰的所有可達的或者可通過的方格,不用管牆啊,水啊,或者其他什麼無效地形,把它們也都加到開放列表中。對於每一個相鄰方格,將點A保存爲它們的“父方格”。當我們要回溯路徑的時候,父方格是一個很重要的元素。稍後我們將詳細解釋它。

3.
從開放列表中去掉方格A,並把A加入到一個“封閉列表”中。封閉列表存放的是你現在不用再去考慮的方格。

此時你將得到如下圖所示的樣子。在這張圖中,中間深綠色的方格是你的起始方格,所有相鄰方格目前都在開放列表中,並且以亮綠色描邊。每個相鄰方格有一個灰色的指針指向它們的父方格,即起始方格。


[圖二]
 

接下來,我們在開放列表中選一個相鄰方格並再重複幾次如前所述的過程。但是我們該選哪一個方格呢?具有最小F值的那個。

路徑排序

決定哪些方格會形成路徑的關鍵是下面這個等式:
F = G + H
這裏


  • G=從起點A沿着已生成的路徑到一個給定方格的移動開銷。

  • H=從給定方格到目的方格的估計移動開銷。這種方式常叫做試探,有點困惑人吧。其實之所以叫做試探法是因爲這只是一個猜測。在找到路徑之前我們實際上並不知道實際的距離,因爲任何東西都有可能出現在半路上(牆啊,水啊什麼的)。本文中給出了一種計算H值的方法,網上還有很多其他文章介紹的不同方法。

我們要的路徑是通過反覆遍歷開放列表並選擇具有最小F值的方格來生成的。本文稍後將詳細討論這個過程。我們先進一步看看如何計算那個等式。

如前所述,G是從起點A沿着已生成的路徑到一個給定方格的移動開銷,在本例中,我們指定每一個水平或者垂直移動的開銷爲10,對角線移動的開銷爲14。因爲對角線的實際距離是2的平方根(別嚇到啦),或者說水平及垂直移動開銷的1.414倍。爲了簡單起見我們用了10和14這兩個值。比例大概對就好,我們還因此避免了平方根和小數的計算。這倒不是因爲我們笨或者說不喜歡數學,而是因爲對電腦來說,計算這樣的數字也要快很多。不然的話你會發現尋找路徑會非常慢。

我們要沿特定路徑計算給定方格的G值,辦法就是找出該方格的父方格的G值,並根據與父方格的相對位置(斜角或非斜角方向)來給這個G值加上14或者10。在本例中這個方法將隨着離起點方格越來越遠計算的方格越來越多而用得越來越多。

有很多方法可以用來估計H值。我們用的這個叫做曼哈頓(Manhattan)方法,即計算通過水平和垂直方向的平移到達目的地所經過的方格數乘以10來得到H值。之所以叫Manhattan方法是因爲這就像計算從一個地方移動到另一個地方所經過的城市街區數一樣,而通常你是不能斜着穿過街區的。重要的是,在計算H值時並不考慮任何障礙物。因爲這是對剩餘距離的估計值而不是實際值(通常是要保證估計值不大於實際值――譯者注)。這就是爲什麼這個方式被叫做試探法的原因了。想要了解更多些嗎?這裏還有更多式子和關於試探法的額外說明。

G和H相加就得到了F。第一步搜索所得到的結果如下圖所示。每個方格里都標出了F、G和H值。如起點方格右側的方格標出的,左上角顯示的是F值,左下角是G值,右下角是H值。


[圖三]
 

我們來看看這些方格吧。在有字母的方格中,G=10,這是因爲它在水平方向上離起點只有一個方格遠。起點緊挨着的上下左右都具有相同的G值10。對角線方向的方塊G值都是14。

H值通過估算到紅色目標方格的曼哈頓距離而得出。用這種方法得出的起點右側方格到紅色方格有3個方格遠,則該方格H值就是30。上面那個方格有4個方格遠(注意只能水平和垂直移動),H就是40。你可以大概看看其他方格的H值是怎麼計算出來的。

每一個方格的F值,當然就不過是G和H值之和了。

 

繼續搜索

爲了繼續搜索,我們簡單的從開放列表中選擇具有最小F值的方格,然後對選中的方格進行如下操作:

4.
將其從開放列表中移除,並加到封閉列表中。

5.
檢驗所有的相鄰方格,忽略那些不可通過的或者已經在封閉列表裏的方格。如果這個相鄰方格不在開放列表中,就把它添加進去。並將當前選定方格設爲新添方格的父方格。

6.
如果某個相鄰方格已經在開放列表中了(意味着已經探測過,而且已經設置過父方格――譯者),就看看有沒有到達那個方格的更好的路徑。也就是說,如果從當前選中方格到那個方格,會不會使那個方格的G值更小。如果不能,就不進行任何操作。
相反的,如果新路徑的G值更小,就將該相鄰方格的父方格重設爲當前選中方格。(在上圖中是改變其指針的方向爲指向選中方格。最後,重新計算那個相鄰方格的F和G值。如果你看糊塗了,下面會有圖解說明。


好啦,咱們來看看具體點的例子。在初始時的9個方塊中,當開始方格被加到封閉列表後,開放列表裏還剩8個方格。在這八個方格當中,位於起點方格右邊的那個方格具有最小的F值40。所以我們選擇這個方格作爲下一個中心方格。下圖中它以高亮的藍色表示。


[圖四]
 

首先,我們將選中的方格從開放列表中移除,並加入到封閉列表中(所以用亮藍色標記)。然後再檢驗它的相鄰節點。那麼在它緊鄰的右邊的方格都是牆,所以不管它們。左邊挨着的是起始方格,而起始方格已經在封閉列表中了,所以我們也不管它。

其他四個方格已經在開放列表中,那麼我們就要檢驗一下如果路徑經由當前選中方格到那些方格的話會不會更好,當然,是用G值作爲參考。來看看選中方格右上角的那一個方格,它當前的G值是14,如果我們經由當前節點再到達那個方格的話,G值會是20(到當前方格的G值是10,然後向上移動一格就再加上10)。爲20的G值比14大,因此這樣的路徑不會更好。你看看圖就會容易理解些。顯然從起始點沿斜角方向移動到那個方格比先水平移動一格再垂直移動一格更直接。

當我們按如上過程依次檢驗開放列表中的所有四個方格後,會發現經由當前方格的話不會形成更好的路徑,那我們就保持目前的狀況不變。現在我們已經處理了所有相鄰方格,準備到下一個方格吧。

我們再遍歷一下開放列表,目前只有7個方格了。我們挑個F值最小的吧。有趣的是,目前這種情況下,有兩個F值爲54的方格。那我們怎麼選擇呢?其實選哪個都沒關係,要考慮到速度的話,選你最近加到開放列表中的那一個會更快些。當離目的地越來越近的時候越偏向於選最後發現的方格。實際上這個真的沒關係(對待這個的不同造成了兩個版本的A*算法得到等長的不同路徑)。

那我們選下面的那個好了,就是起始方格右邊的,下圖所示的那個。


[圖五]
 

這一次,在我們檢驗相鄰方格的時候發現右邊緊挨的那個是牆,就不管它了。上面挨着的那個也同樣忽略。還有右邊牆下面那個方格我們也不管。爲什麼呢?因爲你不可能切穿牆角直接到達那個格子。實際上你得先向下走然後再通過那個方格。這個過程中是繞着牆角走。(注意:穿過牆角的這個規則是可選的,取決於你的節點是如何放置的。)

那麼還剩下其他五個相鄰方格。當前方格的下面那兩個還不在開放列表中,那我們把它們加進去並且把當前方格作爲它們的父方格。其他三個中有兩個已經在封閉列表中了(兩個已經在圖中用亮藍色標記了,起始方格,上面的方格),所以就不用管了。最後那個,當前方格左邊挨着的,要檢查一下經由當前節點到那裏會不會降低它的G值。結果不行,所以我們又處理完畢了,然後去檢驗開放列表中的下一個格子。

重複這個過程直到我們把目的方格加入到開放列表中了,那時候看起來會像下圖這個樣子。


[圖六]
 

注意到沒?起始方格下兩格的位置,那裏的格子已經和前一張圖不一樣了。之前它的G值是28並且指向右上方的那個方格。現在它的G值變成了20並且指向了正上方的方格。這個改變是在搜索過程中,它的G值被覈查時發現在某個新路徑下可以變得更小時發生的。然後它的父方格也被重設並且重新計算了G值和F值。在本例中這個改變看起來好像不是很重要,但是在很多種情況下這種改變會使到達目標的最佳路徑變得非常不同。

那麼我們怎樣來自動得出實際路徑的呢?很簡單,只要從紅色目標方格開始沿着每一個方格的指針方向移動,依次到達它們的父方格,最終肯定會到達起始方格。那就是你的路徑!如下圖所示。從A方格到B方格的移動就差不多是沿着這個路徑從每個方格中心(節點)移動到另一個方格中心,直到抵達終點。簡單吧!


[圖七]
 

A*算法總結

1.
將開始節點放入開放列表(開始節點的F和G值都視爲0);

2.
重複一下步驟:

            i.
在開放列表中查找具有最小F值的節點,並把查找到的節點作爲當前節點;

           ii.
當前節點從開放列表刪除, 加入到封閉列表;

         iii.
當前節點相鄰的每一個節點依次執行以下步驟:

1.
如果該相鄰節點不可通行或者該相鄰節點已經在封閉列表中,則什麼操作也不執行,繼續檢驗下一個節點;

2.
如果該相鄰節點不在開放列表中,則將該節點添加到開放列表中, 並將該相鄰節點的父節點設爲當前節點,同時保存該相鄰節點的G和F值;

3.
如果該相鄰節點在開放列表中, 則判斷若經由當前節點到達該相鄰節點的G值是否小於原來保存的G值,若小於,則將該相鄰節點的父節點設爲當前節點,並重新設置該相鄰節點的G和F值.

        iv.
循環結束條件:

終點節點被加入到開放列表作爲待檢驗節點時, 表示路徑被找到,此時應終止循環;
或者當開放列表爲空,表明已無可以添加的新節點,而已檢驗的節點中沒有終點節點則意味着路徑無法被找到,此時也結束循環;
3.
終點節點開始沿父節點遍歷, 並保存整個遍歷到的節點座標,遍歷所得的節點就是最後得到的路徑;

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章