日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

路径规划之 A* 算法

發布時間:2024/8/23 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 路径规划之 A* 算法 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

算法介紹

A*(念做:A Star)算法是一種很常用的路徑查找和圖形遍歷算法。它有較好的性能和準確度。本文在講解算法的同時也會提供Python語言的代碼實現,并會借助matplotlib庫動態的展示算法的運算過程。

A*算法最初發表于1968年,由Stanford研究院的Peter Hart, Nils Nilsson以及Bertram Raphael發表。它可以被認為是Dijkstra算法的擴展。

由于借助啟發函數的引導,A*算法通常擁有更好的性能。

廣度優先搜索

為了更好的理解A*算法,我們首先從廣度優先(Breadth First)算法講起。

正如其名稱所示,廣度優先搜索以廣度做為優先級進行搜索。

從起點開始,首先遍歷起點周圍鄰近的點,然后再遍歷已經遍歷過的點鄰近的點,逐步的向外擴散,直到找到終點。

這種算法就像洪水(Flood fill)一樣向外擴張,算法的過程如下圖所示:

在上面這幅動態圖中,算法遍歷了圖中所有的點,這通常沒有必要。對于有明確終點的問題來說,一旦到達終點便可以提前終止算法,下面這幅圖對比了這種情況:

在執行算法的過程中,每個點需要記錄達到該點的前一個點的位置 -- 可以稱之為父節點。這樣做之后,一旦到達終點,便可以從終點開始,反過來順著父節點的順序找到起點,由此就構成了一條路徑。

Dijkstra算法

Dijkstra算法是由計算機科學家Edsger W. Dijkstra在1956年提出的。

Dijkstra算法用來尋找圖形中節點之間的最短路徑。

考慮這樣一種場景,在一些情況下,圖形中相鄰節點之間的移動代價并不相等。例如,游戲中的一幅圖,既有平地也有山脈,那么游戲中的角色在平地和山脈中移動的速度通常是不相等的。

在Dijkstra算法中,需要計算每一個節點距離起點的總移動代價。同時,還需要一個優先隊列結構。對于所有待遍歷的節點,放入優先隊列中會按照代價進行排序。

在算法運行的過程中,每次都從優先隊列中選出代價最小的作為下一個遍歷的節點。直到到達終點為止。

下面對比了不考慮節點移動代價差異的廣度優先搜索與考慮移動代價的Dijkstra算法的運算結果:

當圖形為網格圖,并且每個節點之間的移動代價是相等的,那么Dijkstra算法將和廣度優先算法變得一樣。

最佳優先搜索

在一些情況下,如果我們可以預先計算出每個節點到終點的距離,則我們可以利用這個信息更快的到達終點。

其原理也很簡單。與Dijkstra算法類似,我們也使用一個優先隊列,但此時以每個節點到達終點的距離作為優先級,每次始終選取到終點移動代價最小(離終點最近)的節點作為下一個遍歷的節點。這種算法稱之為最佳優先(Best First)算法。

這樣做可以大大加快路徑的搜索速度,如下圖所示:

但這種算法會不會有什么缺點呢?答案是肯定的。

因為,如果起點和終點之間存在障礙物,則最佳優先算法找到的很可能不是最短路徑,下圖描述了這種情況。

A*算法

對比了上面幾種算法,最后終于可以講解本文的重點:A*算法了。

下面的描述我們將看到,A*算法實際上是綜合上面這些算法的特點于一身的。

A*算法通過下面這個函數來計算每個節點的優先級。

?

f(n)=g(n)+h(n)

其中:

  • f(n)?是節點n的綜合優先級。當我們選擇下一個要遍歷的節點時,我們總會選取綜合優先級最高(值最小)的節點。
  • g(n)?是節點n距離起點的代價。
  • h(n)?是節點n距離終點的預計代價,這也就是A*算法的啟發函數。關于啟發函數我們在下面詳細講解。

A*算法在運算過程中,每次從優先隊列中選取f(n)值最小(優先級最高)的節點作為下一個待遍歷的節點。

另外,A*算法使用兩個集合來表示待遍歷的節點,與已經遍歷過的節點,這通常稱之為open_set和close_set。

完整的A*算法描述如下:

* 初始化open_set和close_set; * 將起點加入open_set中,并設置優先級為0(優先級最高); * 如果open_set不為空,則從open_set中選取優先級最高的節點n:* 如果節點n為終點,則:* 從終點開始逐步追蹤parent節點,一直達到起點;* 返回找到的結果路徑,算法結束;* 如果節點n不是終點,則:* 將節點n從open_set中刪除,并加入close_set中;* 遍歷節點n所有的鄰近節點:* 如果鄰近節點m在close_set中,則:* 跳過,選取下一個鄰近節點* 如果鄰近節點m也不在open_set中,則:* 設置節點m的parent為節點n* 計算節點m的優先級* 將節點m加入open_set中

啟發函數

上面已經提到,啟發函數會影響A*算法的行為。

  • 在極端情況下,當啟發函數h(n)始終為0,則將由g(n)決定節點的優先級,此時算法就退化成了Dijkstra算法。
  • 如果h(n)始終小于等于節點n到終點的代價,則A*算法保證一定能夠找到最短路徑。但是當h(n)的值越小,算法將遍歷越多的節點,也就導致算法越慢。
  • 如果h(n)完全等于節點n到終點的代價,則A*算法將找到最佳路徑,并且速度很快。可惜的是,并非所有場景下都能做到這一點。因為在沒有達到終點之前,我們很難確切算出距離終點還有多遠。
  • 如果h(n)的值比節點n到終點的代價要大,則A*算法不能保證找到最短路徑,不過此時會很快。
  • 在另外一個極端情況下,如果h(n)相較于g(n)大很多,則此時只有h(n)產生效果,這也就變成了最佳優先搜索。

由上面這些信息我們可以知道,通過調節啟發函數我們可以控制算法的速度和精確度。因為在一些情況,我們可能未必需要最短路徑,而是希望能夠盡快找到一個路徑即可。這也是A*算法比較靈活的地方。

對于網格形式的圖,有以下這些啟發函數可以使用:

  • 如果圖形中只允許朝上下左右四個方向移動,則可以使用曼哈頓距離(Manhattan distance)。
  • 如果圖形中允許朝八個方向移動,則可以使用對角距離。
  • 如果圖形中允許朝任何方向移動,則可以使用歐幾里得距離(Euclidean distance)。

關于距離

曼哈頓距離

如果圖形中只允許朝上下左右四個方向移動,則啟發函數可以使用曼哈頓距離,它的計算方法如下圖所示:

計算曼哈頓距離的函數如下,這里的D是指兩個相鄰節點之間的移動代價,通常是一個固定的常數。

function heuristic(node) =dx = abs(node.x - goal.x)dy = abs(node.y - goal.y)return D * (dx + dy)

對角距離

如果圖形中允許斜著朝鄰近的節點移動,則啟發函數可以使用對角距離。它的計算方法如下:

計算對角距離的函數如下,這里的D2指的是兩個斜著相鄰節點之間的移動代價。如果所有節點都正方形,則其值就是2?D。

function heuristic(node) =dx = abs(node.x - goal.x)dy = abs(node.y - goal.y)return D * (dx + dy) + (D2 - 2 * D) * min(dx, dy)

歐幾里得距離

如果圖形中允許朝任意方向移動,則可以使用歐幾里得距離。

歐幾里得距離是指兩個節點之間的直線距離,因此其計算方法也是我們比較熟悉的:(p2.x?p1.x)2+(p2.y?p1.y)2。其函數表示如下:

function heuristic(node) =dx = abs(node.x - goal.x)dy = abs(node.y - goal.y)return D * sqrt(dx * dx + dy * dy)

算法實現

雖然前面介紹了很多內容,但實際上A*算法并不復雜,實現起來也比較簡單。

下面我們給出一個Python語言的代碼示例。

之所以使用Python語言是因為我們可以借助matplotlib庫很方便的將結果展示出來。在理解了算法之后,通過其他語言實現也并非難事。

算法的源碼可以到我的github上下載:paulQuei/a-star-algorithm。

我們的算法演示的是在一個二維的網格圖形上從起點找尋終點的求解過程。

坐標點與地圖

首先,我們創建一個非常簡單的類來描述圖中的點,相關代碼如下:

# point.pyimport sysclass Point:def __init__(self, x, y):self.x = xself.y = yself.cost = sys.maxsize

接著,我們實現一個描述地圖結構的類。為了簡化算法的描述:

我們選定左下角坐標[0, 0]的點是算法起點,右上角坐標[size - 1, size - 1]的點為要找的終點。

為了讓算法更有趣,我們在地圖的中間設置了一個障礙,并且地圖中還會包含一些隨機的障礙。該類的代碼如下:

# random_map.pyimport numpy as npimport pointclass RandomMap:def __init__(self, size=50): ①self.size = sizeself.obstacle = size//8 ②self.GenerateObstacle() ③def GenerateObstacle(self):self.obstacle_point = []self.obstacle_point.append(point.Point(self.size//2, self.size//2))self.obstacle_point.append(point.Point(self.size//2, self.size//2-1))# Generate an obstacle in the middlefor i in range(self.size//2-4, self.size//2): ④self.obstacle_point.append(point.Point(i, self.size-i))self.obstacle_point.append(point.Point(i, self.size-i-1))self.obstacle_point.append(point.Point(self.size-i, i))self.obstacle_point.append(point.Point(self.size-i, i-1))for i in range(self.obstacle-1): ⑤x = np.random.randint(0, self.size)y = np.random.randint(0, self.size)self.obstacle_point.append(point.Point(x, y))if (np.random.rand() > 0.5): # Random boolean ⑥for l in range(self.size//4):self.obstacle_point.append(point.Point(x, y+l))passelse:for l in range(self.size//4):self.obstacle_point.append(point.Point(x+l, y))passdef IsObstacle(self, i ,j): ⑦for p in self.obstacle_point:if i==p.x and j==p.y:return Truereturn False

這段代碼說明如下:

  • 構造函數,地圖的默認大小是50x50;
  • 設置障礙物的數量為地圖大小除以8;
  • 調用GenerateObstacle生成隨機障礙物;
  • 在地圖的中間生成一個斜著的障礙物;
  • 隨機生成其他幾個障礙物;
  • 障礙物的方向也是隨機的;
  • 定義一個方法來判斷某個節點是否是障礙物;
  • 算法主體

    有了基本的數據結構之后,我們就可以開始實現算法主體了。

    這里我們通過一個類來封裝我們的算法。

    首先實現一些算法需要的基本函數,它們如下:

    # a_star.pyimport sys import timeimport numpy as npfrom matplotlib.patches import Rectangleimport point import random_mapclass AStar:def __init__(self, map):self.map=mapself.open_set = []self.close_set = []def BaseCost(self, p):x_dis = p.xy_dis = p.y# Distance to start pointreturn x_dis + y_dis + (np.sqrt(2) - 2) * min(x_dis, y_dis)def HeuristicCost(self, p):x_dis = self.map.size - 1 - p.xy_dis = self.map.size - 1 - p.y# Distance to end pointreturn x_dis + y_dis + (np.sqrt(2) - 2) * min(x_dis, y_dis)def TotalCost(self, p):return self.BaseCost(p) + self.HeuristicCost(p)def IsValidPoint(self, x, y):if x < 0 or y < 0:return Falseif x >= self.map.size or y >= self.map.size:return Falsereturn not self.map.IsObstacle(x, y)def IsInPointList(self, p, point_list):for point in point_list:if point.x == p.x and point.y == p.y:return Truereturn Falsedef IsInOpenList(self, p):return self.IsInPointList(p, self.open_set)def IsInCloseList(self, p):return self.IsInPointList(p, self.close_set)def IsStartPoint(self, p):return p.x == 0 and p.y ==0def IsEndPoint(self, p):return p.x == self.map.size-1 and p.y == self.map.size-1

    這里的函數說明如下:

    • __init__:類的構造函數。
    • BaseCost:節點到起點的移動代價,對應了上文的g(n)。
    • HeuristicCost:節點到終點的啟發函數,對應上文的h(n)。由于我們是基于網格的圖形,所以這個函數和上一個函數用的是對角距離。
    • TotalCost:代價總和,即對應上面提到的f(n)。
    • IsValidPoint:判斷點是否有效,不在地圖內部或者障礙物所在點都是無效的。
    • IsInPointList:判斷點是否在某個集合中。
    • IsInOpenList:判斷點是否在open_set中。
    • IsInCloseList:判斷點是否在close_set中。
    • IsStartPoint:判斷點是否是起點。
    • IsEndPoint:判斷點是否是終點。

    有了上面這些輔助函數,就可以開始實現算法主邏輯了,相關代碼如下:

    # a_star.py def RunAndSaveImage(self, ax, plt):start_time = time.time()start_point = point.Point(0, 0)start_point.cost = 0self.open_set.append(start_point)while True:index = self.SelectPointInOpenList()if index < 0:print('No path found, algorithm failed!!!')returnp = self.open_set[index]rec = Rectangle((p.x, p.y), 1, 1, color='c')ax.add_patch(rec)self.SaveImage(plt)if self.IsEndPoint(p):return self.BuildPath(p, ax, plt, start_time)del self.open_set[index]self.close_set.append(p)# Process all neighborsx = p.xy = p.yself.ProcessPoint(x-1, y+1, p)self.ProcessPoint(x-1, y, p)self.ProcessPoint(x-1, y-1, p)self.ProcessPoint(x, y-1, p)self.ProcessPoint(x+1, y-1, p)self.ProcessPoint(x+1, y, p)self.ProcessPoint(x+1, y+1, p)self.ProcessPoint(x, y+1, p)

    這段代碼應該不需要太多解釋了,它就是根據前面的算法邏輯進行實現。為了將結果展示出來,我們在算法進行的每一步,都會借助于matplotlib庫將狀態保存成圖片。

    上面這個函數調用了其他幾個函數代碼如下:

    # a_star.py def SaveImage(self, plt):millis = int(round(time.time() * 1000))filename = './' + str(millis) + '.png'plt.savefig(filename)def ProcessPoint(self, x, y, parent):if not self.IsValidPoint(x, y):return # Do nothing for invalid pointp = point.Point(x, y)if self.IsInCloseList(p):return # Do nothing for visited pointprint('Process Point [', p.x, ',', p.y, ']', ', cost: ', p.cost)if not self.IsInOpenList(p):p.parent = parentp.cost = self.TotalCost(p)self.open_set.append(p)def SelectPointInOpenList(self):index = 0selected_index = -1min_cost = sys.maxsizefor p in self.open_set:cost = self.TotalCost(p)if cost < min_cost:min_cost = costselected_index = indexindex += 1return selected_indexdef BuildPath(self, p, ax, plt, start_time):path = []while True:path.insert(0, p) # Insert firstif self.IsStartPoint(p):breakelse:p = p.parentfor p in path:rec = Rectangle((p.x, p.y), 1, 1, color='g')ax.add_patch(rec)plt.draw()self.SaveImage(plt)end_time = time.time()print('===== Algorithm finish in', int(end_time-start_time), ' seconds')

    這三個函數應該是比較容易理解的:

    • SaveImage:將當前狀態保存到圖片中,圖片以當前時間命名。
    • ProcessPoint:針對每一個節點進行處理:如果是沒有處理過的節點,則計算優先級設置父節點,并且添加到open_set中。
    • SelectPointInOpenList:從open_set中找到優先級最高的節點,返回其索引。
    • BuildPath:從終點往回沿著parent構造結果路徑。然后從起點開始繪制結果,結果使用綠色方塊,每次繪制一步便保存一個圖片。

    測試入口

    最后是程序的入口邏輯,使用上面寫的類來查找路徑:

    # main.pyimport numpy as np import matplotlib.pyplot as pltfrom matplotlib.patches import Rectangleimport random_map import a_starplt.figure(figsize=(5, 5))map = random_map.RandomMap() ①ax = plt.gca() ax.set_xlim([0, map.size]) ② ax.set_ylim([0, map.size])for i in range(map.size): ③for j in range(map.size):if map.IsObstacle(i,j):rec = Rectangle((i, j), width=1, height=1, color='gray')ax.add_patch(rec)else:rec = Rectangle((i, j), width=1, height=1, edgecolor='gray', facecolor='w')ax.add_patch(rec)rec = Rectangle((0, 0), width = 1, height = 1, facecolor='b') ax.add_patch(rec) ④rec = Rectangle((map.size-1, map.size-1), width = 1, height = 1, facecolor='r') ax.add_patch(rec) ⑤plt.axis('equal') ⑥ plt.axis('off') plt.tight_layout() #plt.show()a_star = a_star.AStar(map) a_star.RunAndSaveImage(ax, plt) ⑦

    這段代碼說明如下:

  • 創建一個隨機地圖;
  • 設置圖像的內容與地圖大小一致;
  • 繪制地圖:對于障礙物繪制一個灰色的方塊,其他區域繪制一個白色的的方塊;
  • 繪制起點為藍色方塊;
  • 繪制終點為紅色方塊;
  • 設置圖像的坐標軸比例相等并且隱藏坐標軸;
  • 調用算法來查找路徑;
  • 由于我們的地圖是隨機的,所以每次運行的結果可能會不一樣,下面是我的電腦上某次運行的結果:

    如果感興趣這篇文章中的動圖是如何制作的,請看我的另外一篇文章:使用Matplotlib繪制3D圖形 - 制作動圖。

    算法變種

    A*算法有不少的變種,這里我們介紹最主要的幾個。

    更多的內容請以訪問維基百科:A* Variants。

    ARA*

    ARA?全稱是Anytime Repairing A*,也稱為Anytime A。

    與其他Anytime算法一樣,它具有靈活的時間成本,即使在它結束之前被中斷,也可以返回路徑查找或圖形遍歷問題的有效解決方案。方法是在逐步優化之前生成快速,非最優的結果。

    在現實世界的規劃問題中,問題的解決時間往往是有限的。與時間相關的規劃者對這種情況都會比較熟悉:他們能夠快速找到可行的解決方案,然后不斷努力改進,直到時間用完為止。

    啟發式搜索ARA*算法,它根據可用的搜索時間調整其性能邊界。它首先使用松散邊界快速找到次優解,然后在時間允許的情況下逐漸收緊邊界。如果有足夠的時間,它會找到可證明的最佳解決方方案。在改進其約束的同時,ARA*重復使用以前的搜索工作,因此,比其他隨時搜索方法更有效。

    與A*算法不同,Anytime A*算法最重要的功能是,它們可以被停止,然后可以隨時重啟。該方法使用控制管理器類來處理時間限制以及停止和重新啟動A*算法以找到初始的,可能是次優的解決方案,然后繼續搜索改進的解決方案,直到達到可證明的最佳解決方案。

    關于ARA*的更多內容可以閱讀這篇論文:

    • ARA?- Anytime A?with Provable Bounds on Sub-Optimality。

    D*

    D*是Dynamic A*的簡寫,其算法和A*類似,不同的是,其代價的計算在算法運行過程中可能會發生變化。

    D*包含了下面三種增量搜索算法:

    • 原始的D*由Anthony Stentz發表。
    • Focussed D*由Anthony Stentz發表,是一個增量啟發式搜索算法,結合了A*和原始D*的思想。
    • D?Lite是由Sven Koenig和Maxim Likhachev基于LPA構建的算法。

    所有三種搜索算法都解決了相同的基于假設的路徑規劃問題,包括使用自由空間假設進行規劃。在這些環境中,機器人必須導航到未知地形中的給定目標坐標。它假設地形的未知部分(例如:它不包含障礙物),并在這些假設下找到從當前坐標到目標坐標的最短路徑。

    然后機器人沿著路徑行進。當它觀察到新的地圖信息(例如以前未知的障礙物)時,它會將信息添加到其地圖中,并在必要時將新的最短路徑從其當前坐標重新添加到給定的目標坐標。它會重復該過程,直到達到目標坐標或確定無法達到目標坐標。在穿越未知地形時,可能經常發現新的障礙,因此重新計劃需要很快。增量(啟發式)搜索算法通過使用先前問題的經驗來加速搜索當前問題,從而加速搜索類似搜索問題的序列。假設目標坐標沒有改變,則所有三種搜索算法都比重復的A*搜索更有效。

    D*及其變體已廣泛用于移動機器人和自動車輛導航。當前系統通?;贒* Lite而不是原始D*或Focussed D*。

    關于D*的更多內容可以閱讀這兩篇文章:

    • Project "Fast Replanning (Incremental Heuristic Search)"
    • Real-Time Replanning in Dynamic and Unknown Environments

    Field D*

    Field D*擴展了D*和D* Lite,是一種基于插值( interpolation-based )的規劃算法,它使用線性插值來有效地生成低成本路徑,從而消除不必要的轉向。

    在給定線性插值假設的情況下,路徑是最優的,并且在實踐中非常有效。該算法目前被各種現場機器人系統使用。

    關于Field D*的詳細內容可以看下面這篇論文:

    • Field D*: An Interpolation-based Path Planner and Replanner

    Block A*

    Block A*擴展自A*,但它操作是一塊(block)單元而不是單個單元。

    其open_set中的每個條目都是已到達但尚未擴展的塊,或者需要重新擴展的塊。

    open_set中塊的優先級稱為其堆值(heap value)。與A*類似,Block A*中的基本循環是刪除具有最低堆值的條目并將其展開。在擴展期間使用LDDB來計算正在擴展的塊中的邊界單元的g值。

    LDDB是一種新型數據庫,它包含了本地鄰域邊界點之間的距離。

    關于Block A*的更多內容可以看下面這篇論文:

    • Block A*: Database-Driven Search with Applications in Any-angle Path-Planning

    ?

    原文鏈接
    本文為云棲社區原創內容,未經允許不得轉載。

    總結

    以上是生活随笔為你收集整理的路径规划之 A* 算法的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。