//注1:Mind & Hand,MIT校訓,這里指的理解與實現(動腦也動手)
//注2:博文分為兩部分:(1)理解部分,為參考其他優秀博文的摘要梳理;(2)代碼部分,是C++代碼實現的,源碼來源GitHub開源代碼。
1,建立模型,簡化問題
我一名2018級的Postgraduate新生,路徑規划算法的初學者,這里的理解主要參照資深IT博主“莫水千流”的博客原文(鏈接附在本部分內容的結尾處),做一些梳理和筆記摘要,加深對於A星尋路算法的理解。學習從模仿開始。


原始問題:懶惰的“小貓”為了尋找到達“骨頭”的最短路徑,它應該怎么走?
建立模型:尋路的第一步,把“原始問題的區域”(左圖)簡化成“容易控制的搜索區域”(右圖)。
例如,我們可以將搜索區域划分成“像素點(pixel)”,"划分粒度”的大小取決於具體問題的需要;“划分粒度“的形狀可以選擇正方形(三角形或者六邊形等),這里正方形最簡單區符合需求。
接着,搜索區域可以簡單的用一個地圖大小的二維數組去表示。所以如果是25*25方塊大小的地圖,我們的搜索區域將會是一個有625 個正方形的數組。在這個簡單的例子中,6*7個方塊 = 42 個方塊),所以我們使用一個大小為6*7=42的二維數組來表示搜索區域。
2,A星算法原理與數據結構
2.1 Open和Closed列表
既然我們創建了一個簡單的搜索區域,我們來討論下A星算法的工作原理吧。
除了懶惰之外,我們的貓沒有好的記憶力,所以它需要兩個列表:
-
- 一個記錄下所有被考慮來尋找最短路徑的方塊(稱為open 列表)
- 一個記錄下不會再被考慮的方塊(成為closed列表)
貓首先在closed列表中添加當前位置(我們把這個開始點稱為點 “A”)。然后,把所有與它當前位置相鄰的可通行小方塊添加到open列表中。
下圖是貓在某一位置時的情景(綠色代表open列表):

2.2 路徑增量
現在貓需要判斷在這些選項中,哪項才是最短路徑,但是它要如何去選擇呢?
在A星尋路算法中,通過給每一個方塊一個和值,該值被稱為路徑增量。讓我們看下它的工作原理!
#我們將會給每個方塊一個G+H 和值:
-
- G是從開始點A到當前方塊的移動量。所以從開始點A到相鄰小方塊的移動量為1,該值會隨着離開始點越來越遠而增大。
- H是從當前方塊到目標點(我們把它稱為點B,代表骨頭!)的移動量估算值。這個常被稱為探視,因為我們不確定移動量是多少 – 僅僅是一個估算值。
你也許會對“移動量”感興趣。在游戲中,這個概念很簡單 – 僅僅是方塊的數量。
然而,在游戲中你可以對這個值做調整。例如:
-
- 如果你允許對角線移動,你可以針對對角線移動把移動量調得大一點。
- 如果你有不同的地形,你可以將相應的移動量調整得大一點 – 例如針對一塊沼澤,水,或者貓女海報:-)
這就是大概的意思 – 現在讓我們詳細分析下如何計算出G和H值。
#關於G值
G是從開始點A到達當前方塊的移動量(在本游戲中是指方塊的數目)。
為了計算出G的值,我們需要從它的前繼(上一個方塊)獲取,然后加1。所以,每個方塊的G值代表了從點A到該方塊所形成路徑的總移動量。
例如,下圖展示了兩條到達不同骨頭的路徑,每個方塊都標有它的G值:

#關於H值
H值是從當前方塊到終點的移動量估算值(在本游戲中是指方塊的數目)。
移動量估算值離真實值越接近,最終的路徑會更加精確。如果估算值停止作用,很可能生成出來的路徑不會是最短的(但是它可能是接近的)。這個題目相對復雜,莫水千流博主在原博客的末尾提供了一個網絡鏈接,對它做了很好的解釋。
為了讓它更簡單,我們將使用“曼哈頓距離方法”(也叫“曼哈頓長”或者“城市街區距離”),它只是計算出距離點B,剩下的水平和垂直的方塊數量,略去了障礙物或者不同陸地類型的數量。
例如,下圖展示了使用“城市街區距離”,從不同的開始點到終點,去估算H的值(黑色字):

2.3 A星算法原理
既然你知道如何計算每個方塊的和值(我們將它稱為F,F=G+H), 我們來看下A星算法的原理。
貓會重復以下步驟來找到最短路徑:
-
- 將方塊添加到open列表中,該列表有最小的和值。且將這個方塊稱為S吧。
- 將S從open列表移除,然后添加S到closed列表中。
- 對於與S相鄰的每一塊可通行的方塊T:
-
- 如果T在closed列表中:不管它。
- 如果T不在open列表中:添加它然后計算出它的和值。
- 如果T已經在open列表中:當我們使用當前生成的路徑到達那里時,檢查F 和值是否更小。如果是,更新它的和值和它的前繼。
如果你對它的工作原理還有點疑惑,不用擔心 – 我們會用例子一步步介紹它的原理。
#貓的路徑
讓我們看下我們的懶貓到達骨頭的行程例子。
在下圖中,我根據以下內容,列出了公式F = G + H 中的每項值:
-
- F(方塊的和值):左上角
- G(從A點到方塊的移動量):左下角
- H(從方塊到B點的估算移動量): 右下角
同時,箭頭指示了到達相應方塊的移動方向。
最后,在每一步中,紅色方塊表示closed列表,綠色方塊表示open列表。
好的,我們開始吧!
#第 1步
第一步,貓會確定相對於開始位置(點A)的相鄰方塊,計算出他們的F和值,然后把他們添加到open列表中:

你會看到每個方塊都列出了H值(有兩個是6,一個是4),注意F值(左上角)是G(左下角)值和H(右下腳)值的和。
同時把小貓所在的塊(走過了)添加到closed列表(紅色方塊表示)。
#第 2 步
在第二步中,貓選擇了F和值最小的方塊,把它添加到closed列表中,然后檢索它的相鄰方塊的相關數值。

現在你將看到擁有最小路徑增量的是F值為5的方塊。貓嘗試添加所有相鄰的方塊到open列表中(然后計算他們的和值),除了貓自身的方塊不能添加以外(因為它已經被添加到了closed列表中)或者它是牆壁方塊(因為它不能通行)。
注意被添加到open列表的兩個新方塊,他們的G值都增加了1,因為他們現在離開始點有2個方塊遠了。你也許需要再計算下“城市街區距離”,以確保你理解了每個新方塊的H值。
#第 3 步
再次,我們選擇了有最小F和值(5)的方塊,繼續重復之前的步驟:

現在,只有一個可能的方塊被添加到open列表中了,因為已經有一個相鄰的方塊在close列表中,其他兩個是牆壁方塊。
#第 4 步
現在我們遇到了一個有趣的情況。正如你之前看到的,有4個方塊的F和值都為7 ——我們要怎么做呢?!
有幾種解決方法可以使用,但是最簡單(快速)的方法是一直跟着最近被添加到open列表中的方塊。現在繼續沿着最近被添加的方塊前進。

這次有兩個可通過的相鄰方塊了,我們還是像之前那樣計算他們的和值。
#第 5步
接着我們選擇了最小和值(7)的方塊,繼續重復之前的步驟:

我們越來越接近終點了!
#第 6步
你現在訓練有素了!我打賭你能夠猜出下一步是下面這樣子了:

我們差不多到終點了,但是這次你看到有兩條到達骨頭的最短路徑提供給我們選擇:

在我們的例子中,有兩條最短路徑:
- 1-2-3-4-5-6
- 1-2-3-4-5-7
選擇哪一條其實沒關系,現在到了真正用代碼實現的時候了。
#第 7 步
讓我們從其中一塊方塊,再重復一遍步驟吧:

終於,骨頭在open列表中了!
#第 8 步
現在目標方塊在open列表中了,算法會把它添加到closed列表中:

然后,算法要做的所有事情就是返回,計算出最終的路徑!

#一只有遠見的貓
在上面的例子中,我們看到當貓在尋找最短路徑時,它經常選擇更好的方塊(那個在它的未來最短路徑上的方塊)- 好像它是一只有遠見的貓!
但是如果貓是盲目的,並且總是選擇第一個添加到它的列表上的方塊,會發生什么事情?
下圖展示了所有在尋找過程中會被使用到的方塊。你會看到貓在嘗試更多的方塊,但是它仍然找到了最短路徑(不是之前的那條,而是另一條等價的):

此時,圖中的紅色方塊不代表最短路徑,它們只是代表在某個時候被選擇為“S”的方塊。
我建議你看着上面的圖,並且嘗試過一遍步驟。這次無論你看到哪個相鄰的方塊,都選擇“最壞”的方式去走。你會發現最后還是找到了最短路徑!
所以你可以看到跟隨一個“錯誤的”方塊是沒有問題的,你仍然會在多次重復嘗試后找到最短路徑。
所以在我們的實現中,我們會按照以下的算法添加方塊到open列表中:
-
- 相鄰的方塊會返回這些順序: 上面/左邊/下面/右邊。
- 當所有的方塊都有相同的和值后,方塊會被添加到open列表中(所以第一個被添加的方塊是第一個被貓挑選的)。
下面是從原路返回的示意圖:

最短的路徑是從終點開始,一步步返回到起點構成的(例子:在終點我們可以看到箭頭指向右邊,所以該方塊的前繼在它的左邊)。
附:
https://www.cnblogs.com/zhoug2020/p/3468167.html A星尋路算法介紹
原博客鏈接地址,博主--莫水千流.
3,代碼部分(C++)
代碼是下載的Github上的開源代碼,我下載下來,驗證是可以成功運行的(截圖如下)。目前我還不能完全讀懂整個代碼,所以無法在文章中進行講解,后期C++功底成長起來,應該可以完全理解的。

下面的截圖是Github的代碼作者ddancode(另一個大牛)的展示頁,大家可以自行下載代碼,嘗試分析和閱讀。我還是在剛剛起步的路上。




附:
https://github.com/daancode/a-star “完整代碼zip”——github下載地址
