A*算法詳解

引子

我們討論一個移動機器人遇到問題:如何移動到指定位置

  • 首先,移動機器人需要有一個地圖,同時知道自己現在在哪兒,同時要知道指定位置在地圖的坐標,中途哪兒有障礙物。這個問題就是位置環境中的自主定位與建圖,也就是SLAM。
  • 其次,我們假設目前是在A點,目標位置是B點。需要求得從A點到B點最短的路徑。這個問題就是路徑規劃。
  • 再次,已知地圖中最短的路徑,我們需要用路徑求得實際的運動參數,這里稱之為軌跡。這個問題就是路徑解析。
  • 最后,有了軌跡,根據軌跡的運動參數驅動移動機器人實際運行。這個問題就是運動控制實現。

這里討論的是路徑規劃問題。而A*算法是廣為使用的解決這個問題的算法。

基本概念

  • 啟發式搜索:啟發式搜索就是在狀態空間中的搜索對每一個搜索的位置進行評估,得到最好的位置,再從這個位置進行搜索直到目標。這樣可以省略大量無畏的搜索路徑,提到了效率。在啟發式搜索中,對位置的估價是十分重要的。采用了不同的估價可以有不同的效果。
  • 估價函數:從當前節點移動到目標節點的預估費用;這個估計就是啟發式的。在尋路問題和迷宮問題中,我們通常用曼哈頓(manhattan)估價函數(下文有介紹)預估費用。
  • start:路徑規劃的起始點,也是機器人當前位置或初始位置A
  • goal:路徑規劃的終點,也是機器人想要到達的位置B
  • g_score:當前點到沿著start點A產生的路徑到A點的移動耗費
  • h_score:不考慮不可通過區域,當前點到goal點B的理論移動耗費
  • f_score:g_score+h_score,通常也寫為F=G+H
  • 開啟列表openset:尋路過程中的待檢索節點列表
  • 關閉列表closeset:不需要再次檢索的節點列表
  • 追溯表comaFrom:存儲父子節點關系的列表,用于追溯生成路徑。

算法解析

如圖1,綠色點為start設為A,紅色點為goal設為B,藍色點為不可通過的障礙物,黑色點為自由區域。目標是規劃從A到B的路徑。


圖1

開始搜索

  • 搜索的從A點開始,首先將A點加入開啟列表,此時取開啟列表中的最小值,初始階段開啟列表中只有A一個節點,因此將A點從開啟列表中取出,將A點加入關閉列表。
  • 取出A點的相鄰點,將相鄰點加入開啟列表。如圖2所示,此時A點即為相鄰點的父節點。圖中箭頭指向父節點。將相鄰點與A點加入追溯表中。


    圖2

計算耗費評分

對相鄰點,一次計算每一點的g_score,h_score,最后得到f_score。如圖3,節點的右下角為g_score值,左下角為h_score值,右上角為f_score。


圖3

選最小值,再次搜索

  • 選出開啟列表中的F值最小的節點,將此節點設為當前節點,移出開啟列表,同時加入關閉列表。如圖4所示。
  • 取出當前點的相鄰點,當相鄰點為關閉點或者墻時,不操作。此外,查看相鄰點是否在開啟列表中,如不在開啟列表中將相鄰點加入開啟列表。如相鄰點已經在開啟列表中,則需要進行G值判定


    圖4

G值判定

  • 對于相鄰點在開啟列表中的,計算相鄰點的G值,計算按照當前路徑的G值與原開啟列表中的G值大小。如果當前路徑G值小于原開啟列表G值,則相鄰點以當前點為父節點,將相鄰點與當前點加入追溯表中。同時更新此相鄰點的H值。如果當前路徑G值大于等于原開啟列表G值,則相鄰點按照原開啟列表中的節點關系,H值不變。因為圖示中,當前點G值比原開啟列表G值大,因此節點關系按照原父子關系和F值。

計算耗費評分,選最小值

  • 此時計算開啟列表中F值最小的點,將此節點設為當前節點,并列最小F值的按添加開啟列表順序,以最新添加為佳。


    圖5

重復搜索判定工作

  • 直到當goal點B加入開啟列表中,則搜索完成。此時事實上生成的路徑并一定是最佳路徑,而是最快計算出的路徑。若判定標準改為當goal點B加入關閉列表中搜索完成,則得出路徑是最佳路徑,但此時計算量較前者大。
  • 當沒有找到goal點,同時開啟列表已空,則搜索不到路徑。結束搜索。


    圖6

生成路徑

  • 由goal點B向上逐級追溯父節點,追溯至起點A,此時各節點組成的路徑即使A*算法生成的最優路徑。


    圖7

算法實現

  1. 初始化參數
  • 起始點start
  • 終點goal
  • h_score
  • g_socre
  • f_score
  • 開啟列表openset
  • 關閉列表closeset
  • 追溯表comeFrom
  1. 程序主體
  • 將起始點start加入開啟列表openset
  • 重復一下工作
    • 尋找開啟列表openset中F值最小的節點,設為當前點current
    • 開啟列表openset中移出當前點current
    • 關閉列表openset中加入當前點current
    • 對當前點的每一個相鄰點neighbor
      • 如果它不可通過或者已經在關閉列表中,略過。否則:
      • 如果它不在開啟列表中,加入開啟列表中
      • 如果在開啟列表中,G值判定,若此路徑G值比之前路徑小,則此相鄰點的父節點為當前點,同時更新G與F值。反之,則保持原來的節點關系與G、F值。
    • 當目標點goal在開啟列表中,則結束程序,此時有路徑生成,此時由goal節點開始逐級追溯上一級父節點,直到追溯到開始節點start,此時各節點即為路徑。
    • 當開啟列表為空,則結束程序,此時沒有路徑

偽代碼

// a* 偽代碼
function A*(start, goal)
    //初始化關閉列表,已判定過的節點,進關閉列表。
    closedSet := {}
    // 初始化開始列表,待判定的節點加入開始列表。
    // 初始openset中僅包括start點。
    openSet := {start}
    // 對每一個節點都只有唯一的一個父節點,用cameFrom集合保存節點的子父關系。  
        //cameFrom(節點)得到父節點。
    cameFrom := the empty map

    // gScore估值集合
    gScore := map with default value of Infinity
    gScore[start] := 0 

    // fScore估值集合
    fScore := map with default value of Infinity
    fScore[start] := heuristic_cost_estimate(start, goal)

    while openSet is not empty
                    //取出F值最小的節點設為當前點
        current := the node in openSet having the lowest fScore[] value
                    //當前點為目標點,跳出循環返回路徑
        if current = goal
            return reconstruct_path(cameFrom, current)

        openSet.Remove(current)
        closedSet.Add(current)

        for each neighbor of current

            if neighbor in closedSet
                continue        // 忽略關閉列表中的節點
            // tentative_gScore作為新路徑的gScore
            tentative_gScore := gScore[current] + dist_between(current, neighbor)
            if neighbor not in openSet  
                openSet.Add(neighbor)
            else if tentative_gScore >= gScore[neighbor]
                continue        //新gScore>=原gScore,則按照原路徑

            // 否則選擇gScore較小的新路徑,并更新G值與F值。同時更新節點的父子關系。
            cameFrom[neighbor] := current
            gScore[neighbor] := tentative_gScore
            fScore[neighbor] := gScore[neighbor] + heuristic_cost_estimate(neighbor, goal)

    return failure
    //從caomeFrom中從goal點追溯到start點,取得路徑節點。
function reconstruct_path(cameFrom, current)
    total_path := [current]
    while current in cameFrom.Keys:
        current := cameFrom[current]
        total_path.append(current)
    return total_path
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,250評論 6 530
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 97,923評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,041評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,475評論 1 308
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,253評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,801評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,882評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,023評論 0 285
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,530評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,494評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,639評論 1 366
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,177評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,890評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,289評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,552評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,242評論 3 389
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,626評論 2 370

推薦閱讀更多精彩內容