A*搜尋演算法概述
編者按:本文作者奇舞團前端開發工程師魏川凱。
A*搜尋演算法(A-star search algorithm)是一種常見且應用廣泛的圖搜尋和尋徑演算法。A*搜尋演算法是通過使用啟發式函式來指導尋路,從而高效的保證找到一條最優路徑。A*搜尋演算法最初的設計是用來解決最短路徑問題。但是,從理論來說A*可以解決大多數的成本代數問題。
A*搜尋演算法於1968年,由史丹佛研究院的Peter Hart,Nils Nilsson以及Bertram Raphael首次發表。
原理
A*搜尋演算法綜合了最佳優先搜尋演算法(Best-first search)和Dijkstra演算法的優點,通過一個成本估算函式來指導路徑的搜尋過程:$$ f(n) = g(n) + h(n) $$ 其中:
$n$:任意一個頂點
$g(n)$:表示起點到任意頂點$n$的實際成本
$h(n)$:是一種啟發式函式,表示從任意頂點$n$到目標點的估算成本
在演算法的每次迭代中,會從一個優先佇列中取出$f(n)$值最小(估算成本最低)的節點作為下次待遍歷的節點。這個優先佇列通常稱為open set。然後相應地更新其領域節點的$f(n)$和$g(n)$值,並將這些領域節點新增到優先佇列中。最後把遍歷過的節點放到一個集合中,稱為close set。直到目標節點的$f(n)$值小於佇列中的任何節點的$f(n)$值為止(或者說直到佇列為空為止)。因為目標點的啟發式函式$h(n)$值為0,所以說目標點的$f(n)$值就是最優路徑的實際成本。
下面為A*搜尋演算法的主流程程式碼:
// heuristicFunction,啟發式函式
function aStar(start, goal, heuristicFunction) {
// 帶估算的節點集合,為一個優先佇列,每次取f(n)值最小的節點
const openSet = new PriorityQueue()
openSet.add(start) // 初始只有起點
const closeSet = [] // 已被估算過的節點集合
const gScore = { [start]: 0 } // g(n)值
const hScore = { [start]: heuristicFunction(start, goal) } // h(n)值
const fScore = { [start]: hScore[start] } // f(n)值
const cameFrom = {} // 記錄當前節點的上一個節點
while(!openSet.isEmpty()) {
const current = openSet.pollFirst()
if(current === goal)
return reconstructPath(cameFrom, goal) // 當前節點為目標點,返回最佳路徑
close_set.add(current)
// neighborNodes,取出current節點的鄰域節點
for(let neighbor of neighborNodes(current)) {
if(close_set.includes(neighbor))
continue
// 從起點到neighor的距離
const tentativeGScore = gScore[current] + distance(neighbor, current)
if(!openSet.includes(neighbor) || tentativeGScore < gScore[neighbor]) {
// 記錄neighbor節點的前一個節點
cameFrom[neighbor] = current
gScore[y] = tentativeGScore
hScore[y] = heuristicFunction(neighbor, goal)
fScore[y] = gScore[neighbor] + hScore[neighbor]
openSet.add(neighbor)
}
}
}
}
function reconstructPath(cameFrom, current) {
const bestPath = [current]
while(cameFrom[current]) {
current = cameFrom[current]
bestPath.unshift(current)
}
return bestPath
}
啟發式函式
啟發式函式作為A*搜尋演算法的核心,對演算法的行為有著重大的影響,具體有以下幾種情況:
當啟發式函式$h(n)$始終為0時,則將由從起點到任意頂點n的距離$g(n)$決定,此時A*演算法將等效於Dijkstra演算法。此時,可以考慮初始化一個值非常大的全域性計數器C,每次處理一個節點時,將C分配給它所有領域節點。每次分配後,再將計數器C減一。節點被發現的越早,其$h(x)$值就越高。從而實現深度優先遍歷。
當$h(n)$始終小於等於頂點n到目標點的實際成本,則A*演算法一定可以求出最優解。但是當$h(n)$的值越小,演算法需要計算的節點越多,演算法效率越低。
當$h(n)$完全等於頂點n到目標點的實際成本,則A*演算法將以較快的速度找到最優解。可惜的是,並非所有場景下都能做到這一點。因為在沒有達到目標點之前,我們很難確切算出距離目標點還有多遠。
當$h(n)$大於頂點n到目標點的實際成本,則A*不能保證找到一條最短路徑,但它執行得更快。
當從起點到任意頂點n的實際成本$g(n)$等於0,或者$h(n)$值遠遠大於$g(n)$時,則只有h(n)起作用,此時演算法演變成最佳優先搜尋演算法,速度最快,但可能得不出最優解。
可以看出,通過調節$h(n)$可以控制演算法的精度和速度。一些情況下,我們未必要找到最佳路徑,而是要高效的找出差不多好的路徑,所以需要權衡,這個我們後面在講。
二維網格地圖中的啟發式函式
在二維網格地圖中,有如下幾種常見的啟發式函式:
如果允許朝四個方向移動,即上下左右,則可以使用曼哈頓距離($L_1\,norm$)
如果允許朝八個方向移動,即增加了對角線方向,則可以使用切比雪夫距離($L_\infty\,norm$)
如果允許朝任何方向移動,則可以使用歐幾里得距離($L_2\,norm$)
曼哈頓距離
曼哈頓距離(Manhattan distance)是指兩點所形成的線段對座標軸產生的投影的長度總和。在向量空間中又稱為L1範數。在直角座標系下,兩點之間的曼哈頓距離為:$$ d(x, y) = |x_1 - x_2| + |y_1 - y_2| $$
因此曼哈頓距離的啟發式函式為:$$ h(n) = D \times (|n.x - goal.x| + |n.y - goal.y|) $$ 其中D是節點移動的單位成本,一般是一個常數。
切比雪夫距離
切比雪夫距離(Chebyshev distance)是指二個點之間的距離定義為其各座標數值差的最大值。在向量空間中又稱為L∞範數。在直角座標系下,兩點之間的切比雪夫距離為:$$ d(x, y) = max(|x_1 - x_2|, |y_1 - y_2|) $$
因此切比雪夫距離的啟發式函式為:$$ h(n) = D \times max(|n.x - goal.x|, |n.y - goal.y|) $$ 上面的函式前提是直線和對角線的移動成本都是D,如果對角線的移動成本不是D,則上面的函式是不準確的,那就需要一個更準確的函式:$$ h(n) = D \times (|n.x - goal.x| + |n.y - goal.y|) + \sqrt{2} D min(|n.x - goal.x|, |n.y - goal.y|) $$
歐幾里得距離
歐幾里得距離(Euclidean distance)是指兩點之間的直線距離。在向量空間中又稱為L2範數。在直角座標系下,兩點之間的歐幾里得距離為:$$ d(x,y)=\sqrt{(x_2 - x_1)^2+(y_2 - y_1)^2} $$
因此歐幾里得距離的啟發式函式為:$$ h(n) = D \times \sqrt{(n.x - goal.x)^2+(n.y - goal.y)^2} $$
鬆弛
前面的章節我們提到,通過調節$h(n)$可以控制演算法的速度和精度,一些情況下,我們可以犧牲最優性,來加快搜尋速度。這時候需要做一些鬆弛操作,以便我們求得相較於最優解(1+ε)倍的次優解。
靜態加權。假設$h(n)$是一個啟發式函式,我們可以用$h_w(n = ε \times h(n), ε > 1)$作為加權後的啟發式函式,由於擴充套件了較少的節點,因此速度加快,找到的路徑的誤差最多是最小路徑的ε倍。
動態加權。通過動態改變權重大小,從而控制搜尋的速度,可以使用如下的啟發式函式:$$ f(n)=g(n)+(1+εω(n))h(n) $$ 其中
w(n)是計算權重的函式,當前搜尋深度較小時,會獲得較大的權重,加快搜尋速度:$$ ω(n) = \begin{cases} 1 - \frac{d(n)}{N} & d(n) \leq N \ 0 & otherwise \end{cases} $$
$d(n)$是搜尋深度,$N$是最終路徑的預期長度,ε是允許的誤差範圍。
通過偏好離目標點最近的節點來加快深度遍歷,以提高速度。使用如下的啟發式函式:
$$ f\alpha(n)=(1+w\alpha(n))f(n) $$
其中
wα(n)是計算權重的函式
$$ w_\alpha(n)=\begin{cases} \lambda & g(\pi(n)) \leq g(\tilde{n}) \ \Lambda & otherwise \end{cases} $$
λ和Λ是$\lambda \leq \Lambda$的常量,$π(n)$是n的父節點,而ñ是要擴充套件的節點。
參考連線
https://en.wikipedia.org/wiki/A*_search_algorithm
http://theory.stanford.edu/~amitp/GameProgramming/AStarComparison.html
關於奇舞週刊
《奇舞週刊》是360公司專業前端團隊「奇舞團」運營的前端技術社群。關注公眾號後,直接傳送連結到後臺即可給我們投稿。
相關文章
- 尋路之 A* 搜尋演算法演算法
- A*搜尋演算法(python)演算法Python
- 演算法總結--搜尋演算法
- 搜尋演算法總結演算法
- Python之 常用查詢演算法:最小項搜尋、順序搜尋、二分搜尋Python演算法
- #演算法#二分搜尋演算法
- 搜尋演算法合集 - By DijkstraPhoenix演算法
- 啟發式搜尋的方式(深度優先,廣度優先)和 搜尋方法(Dijkstra‘s演算法,代價一致搜尋,貪心搜尋 ,A星搜尋)演算法
- Sunday搜尋演算法實現演算法
- BM搜尋演算法C實現演算法
- 【演算法】深度優先搜尋(DFS)演算法
- elasticsearch演算法之搜尋模型(一)Elasticsearch演算法模型
- 淘寶搜尋演算法現狀分析演算法
- 基本演算法——深度優先搜尋(DFS)和廣度優先搜尋(BFS)演算法
- 0基礎學演算法 搜尋篇第一講 深度優先搜尋演算法
- 搜尋系統核心技術概述【1.5w字長文】
- 排名演算法(二)--淘寶搜尋排序演算法分析演算法排序
- [譯] Swift 演算法學院 - KMP 字串搜尋演算法Swift演算法KMP字串
- 004.02 各類搜尋的演算法演算法
- 【譯】Swift演算法俱樂部-暴力字串搜尋Swift演算法字串
- 高階搜尋演算法之迭代加深演算法
- 演算法篇 - 二叉搜尋樹演算法
- 6.1 KMP演算法搜尋機器碼KMP演算法機器碼
- 常用的 PHP 搜尋排序算演算法PHP排序演算法
- SEOMoz:Google搜尋演算法變遷史Go演算法
- 海量資料搜尋---搜尋引擎
- 0演算法基礎學演算法 搜尋篇第二講 BFS廣度優先搜尋的思想演算法
- 搜尋引擎-03-搜尋引擎原理
- 每日一道演算法:搜尋插入位置演算法
- 演算法筆記(廣度優先搜尋)演算法筆記
- 電商搜尋演算法技術的演進演算法
- 深度和廣度優先搜尋演算法演算法
- 【譯】Swift演算法俱樂部-二分搜尋Swift演算法
- 二分搜尋演算法的實現演算法
- JAVA圖搜尋演算法之DFS-BFSJava演算法
- 深度優先搜尋演算法-dfs講解演算法
- 《圖論》——深度優先搜尋演算法(DFS)圖論演算法
- java的回收機制----根搜尋演算法Java演算法