Author:Justin Heyes-Jones
From: http://heyes-jones.com/astar.php
Date:2014.8.16
本文地址:http://www.cnblogs.com/killerlegend/p/3917083.html Translated By KillerLegend
前言:前不久數學建模涉及到一個地圖路徑最優化的模型,最初采用的是Dijkstra算法以及Kruscal最小生成樹等算法進行解決的,后來在網上查找時偶然遇到了A*算法,於是就看了看.期間遇到了這篇介紹A*算法的文章,並且作者已經實現了模板並且開源以供使用,作者的這種精神讓我很贊,代碼已經很成熟,作者給出了使用方法,代碼注釋的還是比較詳細的,代碼寫的也很精彩,很棒!希望你可以仔細體會一下作者的思路.
說明:其中@Cmt-Beg和@Cmt-End是我自己的理解,[@quote]是引用的其他內容.除此之外是原文的翻譯.另外有幾張圖片比較模糊,所以自己就按照原圖又制作了幾張清晰的,以方便你查看.
文章很不錯,於是便翻譯了一下,希望這篇優秀的文章可以讓更多的人看到!如果有哪里翻譯的不當或理解有誤,還懇請你提出,謝謝!
以下是正文:
介紹Introduction
歡迎來到A*算法教程.A*算法經常用於視頻游戲中, 在虛擬世界中給人物導航.這個教程向你介紹A*算法並描述如何實現A*算法.教程的源代碼可以在GitHub上下載.
狀態空間搜索(State space search)
A*是一種搜索算法.通過將世界(world)表示成初始狀態,我們可以求解某些問題,對於世界中每一個要執行的動作,我們可以產生相應的狀態(就像如果我們這樣做,這個世界將會有什么相對的反應).如果你不斷的這樣做直到世界是我們所期望的狀態(一個解決方案),那么從起始點到目標點的路徑就是你所面對的問題的解決方案.在這個教程中,我將會使用狀態空間搜索來尋找兩點間的最短路徑(路徑發現:pathfinding),也會解決一個簡單的滑塊迷宮問題(8迷宮問題).首先讓我們先來看一些人工智能方面的專業術語,這些術語在描述狀態空間搜索時會用到:
一些專業術語(Some terminology)
一個節點就是一個狀態,指代世界可能面對的問題.在路徑搜索中,一個節點是將會是一個二維坐標,表示我們所在的地方.在8迷宮問題中,它是所有滑塊的位置.接下來所有的節點被放在一個圖中,其中,節點之間的連接代表解決一個問題的有效步驟.這些連接稱之為邊.在8迷宮問題中,邊以藍色的線顯示,看圖1.狀態空間搜索這樣解決問題:由起始狀態開始,然后對於每一個節點,通過應用所有可能的移動步驟來展開其下所有的節點.
啟發式和算法(Heuristics and Algorithms)
在此,我們引入一個重要的概念:啟發式.這像一個算法,但是和算法有一個關鍵的區別.一個算法是一系列步驟,你可以按照這些步驟來解決問題,對於有效的輸入,算法總是可以正常工作的.比如,你可以自己寫一個算法來計算兩個數的乘積.而啟發式則不保證一定正常工作,但是它的確很有用,因為當沒有算法的時候,它也可能解決一個問題.我們需要一個啟發式函數來幫助我們減少巨大的搜索問題.我們需要的是在每一個節點使用啟發式函數來評估我們距離目標節點還有多遠.在路徑搜索問題中,我們精確的知道我們距離起始點多遠,因為每一步我們都知道移動的距離,因此我們也可以計算我們到目標點處的精確距離.但是,8迷宮問題是比較困難的.對於一個給定的位置,沒有已知的算法能夠計算出我們需要移動多少步可以到達目標點.因此,衍生出各種啟發式搜索.我所知道的最好的啟發式搜索是Nillson Score,在很多情況下,這個啟發式搜索可以很巧妙地直接引導你到達目標狀態.
花費(Cost)
對於圖中的每一個節點,我們現在考慮啟發式函數,啟發式函數可以評估當前狀態距離目標態多遠.另一個需要關心的是到達我們所處的位置的花費.在路徑發現問題中,我們經常賦予每個方塊一個花費.所有的方塊花費是一樣的,因此每個方塊的花費是1(這句話聽起來有點令人不解,但是想一想小學的時候,我們做的應用題,比如:一項工程,甲每天做1/3,已每天做2/1,那么甲乙合作多少天做完?我記得當時通行的解法就是設工程為單位1,這里的1雖然意義不一樣,當都是一個虛指,因為各個方塊之間的花費沒有差異嘛,所以就用1來表示,沒有為什么).如果我們想要區分不同的地形,我們可以給草地以及泥地更高花費,而給新修建的道路較低的花費.對於一個節點,我們需要加上到達此處的花費,這個僅僅是對此節點以及在圖中其上方所有節點的花費之和.
8迷宮(8 Puzzle)
我們仔細來看一看8迷宮問題.這是一個簡單滑塊迷宮問題,由3x3的網格組成,其中缺失一個滑塊,你可以移動其他滑塊到這個間隙,直到你使迷宮達到目標狀態,見圖1:
圖1:一個簡單的8迷宮狀態空間
迷宮可能有362,880種不同的狀態,為了找到一個解決方案,搜索必須通過這些狀態找到一條路徑.對於搜索的大部分情況,邊(藍色線)的數量是2.這意味着每一級的節點數目是2^d,其中d表示深度.如果解決一個給定狀態的步驟是18,那么在解決方案的層級上就含有262,144個節點.
8迷宮游戲狀態和表示含有9個方塊的列表差不多.作為例子,這兒有兩個狀態.最后一個是目標態,達到這種狀態,我們便發現了解決方案.第一個是可能的起始狀態.
起始狀態: SPACE, A, C, H, B, D, G, F, E
目標狀態: A, B, C, H, SPACE, D, G, F, E
這是迷宮問題問題的規則:如果對於給定的滑塊,其上方,下方,左方或者右方有一個間隙,你便可以移動這個滑塊到此間隙.
為了解決迷宮問題,你需要找到一個路徑,由起始態,穿越圖,到目標態.
有一個樣例代碼用於解決8迷宮問題,代碼放在GitHub上(鏈接已經失效,我就取消了鏈接).
路徑發現
在視頻游戲或者其他路徑發現場景中,你想要在沒有碰撞或者不走太遠的情況下,找到一個狀態空間以及找出如何到達你想到達的地方.A*算法將不僅找到一條路徑,而且如果確實存在這樣一條路徑,它將會找到一條最短的路徑.路徑發現中的一個狀態僅僅是所處世界中的一個位置.在類如吃豆人這樣的的迷宮問題中,你可以使用一個二維的網格來表示所有的東西.起始態是一個2維坐標系,其中魔鬼所處的位置就是搜索的起始點.目標態是我們可以去吃掉豆形人.這是一個實現路徑搜索的樣例代碼(鏈接已經失效).
圖2:路徑分析狀態空間的前三個步驟
A*算法的實現(Implementing A*)
我們現在開始看一看A*算法的操作.我們需要做的是從目標態開始,然后向下產生圖.看一下圖1中的8迷宮問題.從起始態我們可以有多少種移動的方法數?答案是2.因為我們有兩個方向來移動空白塊,因此我們可以展開圖.如果我們僅僅是盲目的產生每一個節點的所有后續節點,我們可能在找到目標節點以前就已經耗盡了計算機的內存.很明顯,我們需要記憶最好的節點,然后搜索這些節點.除此之外,我們也應該知道,我們僅需要記憶已經展開的節點,因為我們不需要重復展開相同的狀態.我們首先創建一個OPEN表.在這個表中,我們記下我們沒有展開的節點.當算法開始的時候,起始態被放在OPEN表中,它是唯一一個我們知道的狀態並且我們還沒有展開它.因此,我們將從起始態展開節點然后將這些展開的節點放在OPEN表中.現在我們已經對起始節點做了這樣的工作,那么我們需要將其放入到CLOSED表中.CLOSED表中存放我們已經展開的節點列表.
f = g + h
使用OPEN和CLOSED列表讓我們對於下一步搜索變得更加有選擇.我們首先想要查看最好的節點.我們將會給每一個節點一個績點,來表示我們認為它的好壞程度.這個績點應該被考慮成從此節點到目標節點的花費加上到達我們所在節點的花費.按照慣例,這些用字母f,g和h來表示.g是到達此節點的所有花費,h是一個啟發函數(評估我們到達目標節點的花費).f是這兩個的和.我們為每一個節點存儲這些信息. 使用f,g和h的值,A*算法將會按照我們的條件,向目標靠近,最終我們將會發現可能的最短路徑.
到目前為止我們已經了解了A*算法的組成元素,接下來讓我們看看這些使用這些元素實現這個算法.
A* pseudocode 1 Create a node containing the goal state node_goal 2 Create a node containing the start state node_start 3 Put node_start on the open list 4 while the OPEN list is not empty 5 { 6 Get the node off the open list with the lowest f and call it node_current 7 if node_current is the same state as node_goal we have found the solution; break from the while loop 8 Generate each state node_successor that can come after node_current 9 for each node_successor of node_current 10 { 11 Set the cost of node_successor to be the cost of node_current plus the cost to get to node_successor from node_current 12 find node_successor on the OPEN list 13 if node_successor is on the OPEN list but the existing one is as good or better then discard this successor and continue 14 if node_successor is on the CLOSED list but the existing one is as good or better then discard this successor and continue 15 Remove occurences of node_successor from OPEN and CLOSED 16 Set the parent of node_successor to node_current 17 Set h to be the estimated distance to node_goal (Using the heuristic function) 18 Add node_successor to the OPEN list 19 } 20 Add node_current to the CLOSED list 21 }
下面是一個對應的中文說明:
1 創建一個包含目標狀態的節點node_goal 2 創建一個包含起始狀態的節點node_start 3 將起始節點放在開放列表(OPEN)中 4 while OPEN 不為空 5 { 6 讓含有最小f值的節點從OPEN表出列,稱之為node_current 7 if node_current 和 node_goal 狀態相同,則我們已經找到解決方案;從while循環break 8 取得來自node_current的每一個后繼節點node_successor的狀態 9 for node_current的每一個后繼節點node_successor 10 { 11 設置node_successor的花費=node_current的花費+從node_current到node_successor的花費 12 在OPEN表中尋找node_successor 13 if node_successor已經在OPEN表中但已經存在的node_successor和其一樣好甚至更好,那么丟棄這個node_successor,然后continue 14 if node_successor已經在CLOSED表中但是已經存在的node_successor和其一樣好甚至更好,那么丟棄這個node_successor,然后continue 15 從OPEN以及CLOSED中移除發現的node_successor 16 將node_successor的父節點設置為node_current 17 將h設置為到目標節點node_goal的估計距離(使用啟發函數) 18 將node_successor添加到OPEN列表中 19 } 20 將node_current添加到CLOSED表中 21}
希望我們在前一段提到的思想可以在我們看A*算法偽代碼的時候有所幫助.為了讓這個算法的操作看起來更加的清晰,我們再來看一看圖1中的8迷宮問題.下面的圖3顯示了每一個滑塊f,g和h的績點.
圖3:顯示了f,g和h績點的8迷宮問題狀態空間(替換了原圖,因為原圖看上去很模糊)
首先看一看每一個節點的g值.它表示的是從起始節點到當前節點的花費.因此圖片中心數字就是g(注:比如17 0 7中的0就表示的是g值).正如你所看到的,這個數字每一級增加1.在某些問題中,狀態發生改變,這花費也可能發生變化.比如在尋找路徑的問題中,有時候某些類型的地形會比其它地形的花費要高.
@Cmt-Beg
g值表示的是該節點處所有滑塊到其正確位置的曼哈頓距離之和.
@Cmt-End
接下來看最后一個數字,也就是h,即啟發績點.正如我在上面說提到的,我使用一個稱之為Nilsson’s Sequence的啟發函數來計算啟發值.這個啟發函數在很多情況下可以迅速的覆蓋一個正確的解決方案.這里將告訴你對於每一個8迷宮狀態如何計算這個績點:
尼爾森序列績點(Nilsson's sequence score)
在中心處的滑塊績點為1(它應該是空的).
對於每一個不在中心處的滑塊,如果其順時針處的滑塊不應該是處於其順時針處,那么其績點為2.將這一系列績點乘以3並且加上所有的滑塊到其正確位置所要移動的距離.
@Cmt-Beg
原文是這樣寫的:
[@quote]
A tile in the center scores 1 (since it should be empty)
For each tile not in the center, if the tile clockwise to it is not the one that should be clockwise to it then score 2. Multiply this sequence by three and finally add the total distance you need to move each tile back to its correct position.
[@quote]
我表示沒有看懂,這話說的到底是什么意思呢?后來我給作者發了郵件,原作者給我了個網址,他說讓我看看http://heuristicswiki.wikispaces.com/Nilsson's+Sequence+Score處對此序列的介紹,這個網址是這樣介紹的:
h(n) = P(n) + 3S(n)
P(n) is the Manhattan Distance of each tile from its proper position.
S(n) is the sequence score obtained by checking around the non-central squares in turn, allotting 2 for every tile not followed by its proper successor and 1 in case that the center is not empty.
也就是說:h(n) = P(n) + 3 S(n),其中:
P(n) :每一個滑塊距離其正確位置的曼哈頓距離之和
S(n) : 通過輪流檢查非中心滑塊所獲得的一個序列績點.對於每一個其后續節點不是合適的后續節點的節點,分配績點2,對於其他所有節點則分配績點0,哦,不過有一個特例,中心節點分配績點1.
似乎看起來很不錯呢,但是,我發誓在我看了之后,我還是沒有懂,到底怎么計算Nilsson序列!
這是我手稿紙啊,畫了大半天,也沒有所以然!
難道是俺英語太次,沒有看懂人家的意思嗎?后來在StackOverflow上看見一位來自英國的伙計說:
當時我就笑了,嗯,看起來很不錯呢!當然下面有回答(英文好的朋友可以看一看: http://stackoverflow.com/questions/10584788/can-anyone-explain-nilssons-sequence-score-in-8-puzzle-more-clearly?answertab=oldest#tab-top),我也是看了這個回答,才算了解了這個讓人感覺好..詭..異..的序列.下面說一說怎么算這個序列,我保證用我認為最詳細的步驟給你說明白:
首先將網格按下圖編號(來個綠色的網格吧,這幾天盯着白花花的電腦屏幕眼睛都看得受不了了..),注意網格的編號順序(這個網格為什么要這么寫,估計是精心設計過的...):
我們約定N(x)表示x方塊在上圖中的編號.
那么Nilsson序列績點有以下偽代碼例程給出:
for each tile x in (A,B,C,...,H) score += distance from N(x) to the correct square for tile x if N(x)==8 # i.e. the tile is in the center score += 3*1 else if N(next(x))!= (N(x)+1)%8 score += 3*2
其中next(x)表示按照順時針順序(依據字母順序:A,B,C..X,Y,Z,A),x下面的值.比如next(A)=B,next(C)=D,next(Z)=A.看上述偽代碼需要注意的是,next(x)對x操作后,x還是原來的x,可能有人會認為next(x)操作x之后,x就是next(x)了..比如我,這樣是不對的,希望你可以避免這個坑!這樣的話,上述偽代碼很清楚的告訴我們Nilsson序列是如何得到,大白話如下:
對於每一個節點(注意在這里,一個節點代表了世界所處的一種狀態,而這種狀態在這兒是一個3x3的表格!),計算每一個的滑塊到其正確位置(目標狀態時此滑塊應處於的位置)所應該移動的距離(准確的說應該是曼哈頓距離),然后將所有滑塊所得到的曼哈頓距離加到一塊兒,這得到的是h(n)=p(n)+3*s(n)中的p(n).
然后是計算s(n).我們還是需要處理節點中的所有滑塊,為了簡單起見,我們按順序來(A->B->..->H),如果這個滑塊為中心滑塊(也就是編號為8),那么得到一個序列績點1,如果這個滑塊為非中心滑塊,那么,若其下一個滑塊為恰當的滑塊(比如,A的下一個滑塊為B,C的下一個滑塊為D,H的下一個滑塊為A,那么這些滑塊都是恰當的,還不懂,沒事兒,一會兒我會舉例子說明),則該滑塊得到一個序列績點2.當遍歷完所有的滑塊后,將序列績點相加便得到了s(n).
得到s(n)和p(n)后,我們將很容易計算得到h(n).
下面舉幾個例子來說明:
先按照偽代碼來說明:
因此s(n)=2+1+2=5,而p(n)的計算是頗為簡單的,因為C,D,E,F,G都在其正確的位置(我們有時也含蓄的稱之為適當的位置^-^),所以需要移動的距離均為零,只需要看一看A,B,H就可以了,顯然各自均要移動一個曼哈頓距離(什么是曼哈頓距離,哈!自己查查..),所以p(n)=3.so,The value of h(n) is 18 which is from 3+3*(2+1+2).
而直觀一點來看的話就是..,看下圖:
我們從A開始,A順時針應該接的是B,可是我們遇到的卻是C,所以A得到一個序列績點2,緊接着是C,C順時針指向的應該是D,正好符合.所以C得到0,同樣的,我們可以得到D,E,F,G均得到0.在G處,G下面沒有了,顯然不合,所以得到序列績點2,然后從H開始,H順時針指向A,符合條件.因此H得到績點為0.B為中心滑塊,績點為1. 所以S(n)=2+2+1=5.p(n)是同樣的算法.我想現在你已經理解了那句話(if the tile clockwise to it is not the one that should be clockwise to it then score 2).
然后再來看一個:
依舊從A開始順時針來計算,顯然A->C使A得到序列績點2,H->nul使H得到績點2,B為中心滑塊,所以B得到績點1.所以s(n)=2+2+1=5.p(n)=2,很好算,我就不說了.因此h=15+2=17.到此,你肯定會說,Nilsson序列這么簡單,哇..當初感覺自己都要freaking out(崩潰)啦!
@Cmt-End
讀源代碼或許會讓這個計算方法更加清晰明了.看一看圖片,你應該感到滿足,因為根據這個算法,所有的h績點都是正確的.
最后看最左邊的數字,也就是f績點.它是f和h的和,在A*算法完成其搜索的過程中,它所完成的正是通過狀態空間來追蹤最小的f值.
看一看教程所提供的源代碼,雖然到目前為止,算法的原理可能在你頭腦中已經很清晰,但是實現或許還是有些復雜的.在此處,我將使用我的源代碼,源代碼用C++編寫,使用了標准庫以及STL數據結構.
C++實現細節(C++ implementation details)
我們可以看到A*算法包含在一個頭文件中,以你為它被實現為模板類.你只需要編譯例子中的文件8puzzle.cpp以及findpath.cpp即可.
源文件中有一些注釋,我希望這些注釋是清晰的且易於理解的.接下來是關於這些文件如何工作的概要以及基本設計思想.
主類叫做AstarSearch,是一個模板類.我之所以選擇模板是因為這可以讓用戶高效的使用AstarSearch.(...省略一堆沒有的)...
你可以傳入一個代表問題狀態的數據類型.這個類型必須包含有數據,這些數據代表了每一種狀態.另外在搜索過程中也有幾個成員函數供調用.描述如下:
float GoalDistanceEstimate( PuzzleState &nodeGoal );
返回此節點到目標節點的估計花費值.
bool IsGoal( PuzzleState &nodeGoal );
如果此節點為目標節點則返回true
void GetSuccessors( AStarSearch *astarsearch );
對於此狀態下的每一個后繼者,調用AstarSearch的AddSuccessor方法增加一個節點到當前的搜索
float GetCost( PuzzleState *successor );
返回從此狀態到后繼狀態的花費值.
bool IsSameState( PuzzleState &rhs );
如果所傳入的狀態和此狀態相同則返回true.你應該可以很容易的實現一個不同的問題.你所需要做的只是創建一個類,該類表示你的問題中的一個狀態.然后自己完成上面的函數.一旦你創建了一個搜索類,比如:AStarSearch astarsearch;
然后創建一個起始以及目標狀態,然后將它們傳到算法中以初始化搜索:
astarsearch.SetStartAndGoalStates( nodeStart, nodeEnd );
每一步(一步所完成的是獲取最優節點以及展開其后續節點)你調用
SearchState = astarsearch.SearchStep();
返回一個狀態,可以讓你知道搜索是否成功,失敗或者仍在進行.一旦搜索成功,你需要將其顯示給用戶,或者在你的程序中進行使用.因此我添加了幾個函數:
UserState *GetSolutionStart();
UserState *GetSolutionNext()
UserState *GetSolutionEnd();
UserState *GetSolutionPrev()
你可以使用它們來在一個解決方案中移動一個內部迭代器.最典型的使用是GetSolutionStart (the start state)以及使用GetSolutionNext迭代每一個節點.對於調試以及一些需要后向迭代的問題,你可以使用后兩個函數.
Debugging and Educational functions
如果你打算顯示每一個步驟中的OPEN以及CLOSED表.這是一個普通的調試功能,可以讓你的算法運作起來.進一步來說,對於學生而言,從這一種方式更加容易理解.在搜索例程中使用下面的函數來襲那是列表:
UserState *GetOpenListStart( float &f, float &g, float &h );
UserState *GetOpenListNext( float &f, float &g, float &h );
UserState *GetClosedListStart( float &f, float &g, float &h );
UserState *GetClosedListNext( float &f, float &g, float &h );
你可以看到這些調用是對f,g以及h的引用,因此如果你在調試或者學習過程中需要看這些變量的值的話,你可以傳入浮點變量來存儲這些值.這些值都是可選參數,你可以不用在意它們.
如果使用這些功能,你可以通過findpath.cpp以及8puzzle.cpp例子來了解.
我希望到此處你已經理解了關鍵概念,通過閱讀以及親自體驗樣例中的代碼(在一個調試器中單步執行是非常直觀的),你完全有希望領悟A*算法.為了完成這篇教程,我將簡短的提一下可容忍性以及優化問題(Admissibility and Optimization issues).
Admissibility(可容忍性)
任何關於圖的搜索算法,如果它總是可以返回一個優化解(也就是說,如果解決方案存在,返回的為最低花費),那么就說它是可以容忍的.然而,A*僅僅決定於你所用到的啟發式函數h,當h沒有過度評估到目標處的距離時,A*算法才是可容忍的.換句話說,如果你知道一個啟發式函數,它總是返回到目標距離的精確值,那么如果h’是看容忍的,那么h’必須小於或者等於h.
考慮到這個因素,你應該總是確保選擇的啟發式函數沒有過度評估到目的點的距離.實際上,有時候,這是不可能的.以8迷宮問題為例,我們上面的啟發式函數可能得到到目標點的距離大於實際情況.但是它有益於你更加深刻的認識這個理論.如果你讓啟發式函數返回零,那么你一定不會得到一個到目標的過度估計值,這種情況下,你搜索了每一步中所產生的所有節點(深度優先搜索).
關於可容忍性最后要說的一點是:對於A*理論還有一個推理,叫做Graceful Decay of Admissibility(容忍性許可衰減?不好翻譯..),這個推論告訴我們,如果你的啟發式函數獲得的評估距離超過真實距離(到目標點處)的大小不超過某一個值(我們稱之為E),那么這個算法將基本上不會找到一個解決方案使超過最優解的花費超過E.
Optimization(優化)
對於A*算法較好的優化可以在Steve Rabin的Game Gems書中找到,另一本是AI Wisdom.這些書中聚焦於路徑發現(在游戲中廣泛使用).
路徑發現優化本身就是一個完整的學科,我僅僅是想實現A*算法以供一般性的使用,但是很明顯有很多地方你可以在你的問題中進行優化.在使用Vtune(Intel的性能分析工具)對我的樣例代碼進行測試后,我發現有兩個主要的瓶頸,第一個是對新節點OPEN和CLOSED列表的搜索,第二個是管理新節點.一個簡單但是有效的優化方案是寫一個比C++ 標准的new更加簡單的內存分配器.我已經為這個類提供了這樣的代碼,你可以在stlastar.h中使用.如果我對此有足夠的興趣的話,我可能會對其寫一個教程.
由於在每一次搜索循環中你總是希望從OPEN列表中獲得具有最小f績點的節點,因此你可以使用一個叫做優先隊列的數據結構(priority queue).這可以讓你很好的管理你的數據,因為你可以總是讓你最好的(也或者是最壞的一些,取決於你如何設置)一項有效的被移除.Steve Rabin的書中(上面提到的那本)給我展示了如何使用STL Vector以及堆操作來實現這個行為.我的源代碼也使用了這個技術.如果你對優先隊列感興趣,你可以使用這個源代碼.我基於優先隊列使用C實現了堆以及鏈表.代碼已經很成熟了,已經在項目FreeCell Solver 中使用.
另一個優化是你應該使用一個hash表而不是搜索列表.這會防止你使用一個線性搜索.第三個優化是,你不需要在一個圖搜索問題中進行回溯.比如,你可以看一下路徑發現,如果你回溯到你的起始處,你絕不會距離目標點更近.因此,當你寫代碼來產生一個節點的后續節點時,你可以檢查已經產生的節點並且評估任何和父節點一樣的狀態.雖然這和算法的操作沒什么區別,但是它確實使回溯加快.
文章靈感來源於:http://www.gamasutra.com/features/19990212/sm_01.htm
文章基本上到此算是說完了,接下來如果有時間我會對作者的源代碼進行剖析,進一步說明A*算法.
英文水平有限,如果有翻譯的不當或者理解有誤,還請您向我提出,或留言,或給我發郵件chinamyth1@gmail.com.謝謝!