開篇
這篇文章介紹找最短路徑的一種算法,它的字我比較喜歡:啟發式搜索。
對於入門的好文章不多,而這篇文章就是為初學者而寫的,很適合入門的一篇。文章定位:非專業性A*文章,很適合入門。
有圖有真相,先給大家看個效果圖吧:從圖的左下角到右上角尋找最短路徑,灰色部分是障礙物。
這是用一般的搜素方法,類似窮舉的效果
下面的圖是用A*搜素的效果,也就是本文要介紹的算法。
可以看出,用A*算法減少了許多計算量,它的效率有了顯著的提高。
下面將為你解答上圖中的算法是如何實現的。
圖片來源:http://en.wikipedia.org/wiki/A*_search_algorithm
正文
搜索區域介紹
下圖是這篇文章討論的中心:
圖中左邊的綠色點是搜索的起點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、保存路徑,從目標點出發,按照父節點指針遍歷,直到找到起點。
后記
其實啟發式搜索就是對窮舉的一種優化,讓每次搜索都更接近目標。這就要通過估值函數實現,對於這類問題,找到一個估值函數是關鍵。
估值函數:從當前點出發到目標點的花費。其實從這個理念上說,好像和分支界限法有些類似,都是在窮舉的基礎上對搜素優化。