1. 程式人生 > >對於A*演算法的學習(啟發式搜尋)

對於A*演算法的學習(啟發式搜尋)

開篇

這篇文章介紹找最短路徑的一種演算法,它的字我比較喜歡:啟發式搜尋。

標題上寫的是翻譯,只是覺得原文講解的思路很清晰。這篇文章整體構思和原文相差不多,只是有些地方有小的改動,

我想的是用更容易理解的方式、更簡潔的把A*演算法的思想呈現出來。

文章中出現的詞openlist,closelist我覺得用原文會更好故沒有翻譯,在文中會有解釋。

各位也可以直接參考原文。

網上關於A*演算法的文章還有很多,只是那些都需要有一定的基礎,對於入門的好文章不多,而這篇文章就是為初學者而寫的,很適合入門的一篇。文章定位:非專業性A*文章,很適合入門。

有圖有真相,先給大家看個效果圖吧:從圖的左下角到右上角尋找最短路徑,灰色部分是障礙物。

這是用一般的搜素方法,類似窮舉的效果

下面的圖是用A*搜素的效果,也就是本文要介紹的演算法。

可以看出,用A*演算法減少了許多計算量,它的效率有了顯著的提高。

下面將為你解答上圖中的演算法是如何實現的。

正文

搜尋區域介紹

下圖是這篇文章討論的中心:

圖中左邊的綠色點是搜尋的起點A,目標點是右邊的紅色點B,中間被障礙物擋住(藍色部分)。

我們的目的是從起點出發,找到一條到達目標點的最短路徑。

把整個圖都分為一個個小方塊只是為了這樣方便討論,更多的應用中,也可以把圖分為其它方塊的組合。

開始搜尋

目標是找出從A點出發到B的最短路徑,所以我們從A點開始搜尋,直到找到目標B。

搜尋的步驟是這樣的:

1、從起點A開始把A加入到openlist中。openlist解釋:它是一個佇列,裡面元素是一些方塊,它們有可能構成最短路徑。現在佇列中只有元 素A,以後會加入更多的元素。以後會對裡的元素進行檢查,從裡面來找到構成最短路徑的元素。

2、看起點A周圍的元素是否可達(是否能從A到達它們)把從A可到達的元素加入到openlist中,並且加入到openlist中的節點維護一個指指標,指向他的父親,也即A點。如果A周圍有障礙物就忽略它。從這個圖看, A周圍把個元素都可達,所以把它們都加到openlist中。

3、把起點A放入closelist中,在closelist中的點意味著以後不需要再去考慮它了。對於A節點,A可達的點都加入到了openlist中,以後也就不用考慮A的情況了。

經過以上三步操作後的效果圖如下所示

圖中被暗綠色包圍的就是openlist中的點,一共八個,都是從起點A可達的點,並且他們中的每個都有一個指向他們父節點的指標(圖中的小針方向)被高亮綠色包圍的表示closelist中的點,可以看出起點A已經在closelist中。

路徑選擇

從起點出發 ,下一步可以走的點現在有八個,選取哪一個作為下一步的點呢?正常的思維是選取一個離目標值最進,且在這些點中離遠點最近的點。

本文的思路也是這樣的,文中用

F = G + H

表示,其中:

對於每個點,都有自己的G、H、F。

其中G表示從特定的點到起點的距離,H表示從該點到目標的估值,那麼F就是經過該點路徑的估值。

下面詳細介紹

G:從起點到特定節點的距離,也就是G的父節點加上從G的父節點到起點A的距離g。圖中是邊長為10的正方形塊,所以就是G的父節點的值g

加上10(上下左右相鄰)或者加上14(斜塊相鄰、也就是對角線的長度,本來是14.14、、為了方便計算這裡取近似值)

H:H能用很多方法得到估計值.這裡用到的方法稱為Manhattan method,H的值就是從考慮的點通過水平和垂直移動達到目標點的移動步數乘10(正方形塊的邊長為10).注意只是水平和垂直移動,不走斜線。並且忽略圖中的障礙物。

插一句:

看了對H的描述,你可能會懷疑這種估計的精確性,有一點是可以肯定的:估計值越接近真實值,演算法就能更塊的找出最短路徑。我們用的這種方法確實是做了估計,只是這種估計準確性不高,就是說只是粗略的估計,因為這種方法容易理解,所以才採用這種方法。可以想到,太過接近的估值最後不一定能得到想要的結果。關於估值函式想了解更多請參見:http://www.policyalmanac.org/games/heuristics.htm

為了從openlist中選取一個點繼續搜尋,就要計算出openlist中的每個點的F、H、G的值然後選取F小的一個點,進行下一步的探索。

對於上圖中的點,他們的F、G、H的值在圖中都有標明。

F、H、G的位置在起點右邊的點中已經有標註,其他點的位置同理。

現在看起點右邊的點(也就是標有字母的點)G=10,因為在起點正左邊。H=30,水平移動三個格子可以到目標點B。F=G+H=40

繼續搜尋

由於我們的目的是找最短路徑 ,下一步就從openlist中選取F最小的點做進一步的搜尋,按如下步驟進行:

(為了方便描述,把選取的點成為點M)

1、檢查M周圍的點,在closelist中則忽略它,如果可達且不在openlist中,則加入openlist中,同理的維護一個指向父節點的指正,同時計算加入點的F H G 值。

2、如果M周圍的點在openlist中,則看從起點A通過M到這類點的路徑是不是小於他們的G值,如果是則更新他們的G、F值(更新為小的)。如果不是則不做任何操作。

3、把M從openlist中移除,加入closelist中。

對openlist中F最小的點(也就是起點左邊的點)的處理效果如下圖所示:

M的右邊、右上、右下是障礙物,所以忽略他們。M的左邊點在closelist中,也不去管他,剩下的是M的上、下、左上、左下的點。他們已經在openlist中,所以看從起點通過M到他們的距離是不是小於他們的G值。通過判斷,都比他們的G值大,所以做任何操作。

可以看出,現在的closelist已有兩個元素了(高亮綠色包圍的塊)

下一步的操作和上面敘述的一樣,從openlist中找出F最小的,重複上的操作。從圖中可以看出,現在的openlist中F最小的有兩個,就是剛剛考慮的點的正上方和正下方,其實這裡選哪個都無所謂,只是人們習慣於選擇較晚加入到openlist中的元素,這裡選擇下方的點。

同理,處理效果如下圖所示:

下面簡單的說下處理過程:

暫且稱現在處理的點為N吧。

N上方  在closelist中,不考慮。

N左方 在openlist中,看從原點通過N到它的距離為14+10大於10,不做操作,跳到下一步

N的左下方,下方 加入openlist中,同時記錄F、G 、H的值還有指向父節點的指標。

N的右下方這裡看做“不可達的點”原因是這兩個點都處於障礙物的對角上,當然這只是一種人為的規定。也可以取消這條規定就把它加入到openlist中。這只是一種規定,不必深究。

處理的結果是closelist中現在有三個元素,用高亮的藍色標記,同樣的,openlist中的元素用暗綠色標記出。

重複上的步驟,每次從openlist中選取F最小的點加入closelist中,同時處理這個點周圍的元素。。

直到目標節點也被加入到closelist中停止。

處理的效果如下圖所示:

如果用心看、你也許已經發現了,在起點正下方兩個點的G值,沒錯,就是圖中用橢圓圈起來的點,之前的G=28,現在是20。這是在演算法進行的時候更新的,可能 是這其中的某一步,處理這個點的時候,發現了一條更短的路徑20,替換了原來的28。

到這裡,問題已經基本解決了,最後的任務就是得到這條路徑。

只要從目標點出發,沿著他們的父節點遍歷,直到起點。就得到了一條最短路徑。

如下圖所示

總結

 現在你應該對A*演算法有一個初步的認識了吧,總結下演算法的實現過程:

1、把起點加入到openlist中

2、重複以下步驟

  a、從openlist中找出F最小的節點,並把它當做當前的操作節點

  b、檢查當前點周圍的點,如果已經在openlist中看是否能通過當前點得到更小的G,如果能就更新那個點的G,F的值,如果在closelist中或者是障礙物(不可達)則忽略他們

  c、把當前點從openlist中移除 ,加入closelist中

  d、當目標點加入closelist中時停止

3、儲存路徑,從目標點出發,按照父節點指標遍歷,直到找到起點。

後記

 其實啟發式搜尋就是對窮舉的一種優化,讓每次搜尋都更接近目標。這就要通過估值函式實現,對於這類問題,找到一個估值函式是關鍵。

估值函式:從當前點出發到目標點的花費。其實從這個理念上說,好像和分支界限法有些類似,都是在窮舉的基礎上對搜素優化。