概念
JPS(jump point search)演算法實際上是對A* 尋路演算法的一個改進,因此在閱讀本文之前需要先了解A*演算法。A* 演算法在擴充套件節點時會把節點所有鄰居都考慮進去,這樣openlist中點的數量會很多,搜尋效率較慢。
若不瞭解A*演算法,可以參考博主以前寫的一篇文章 A* 尋路演算法 - KillerAery - 部落格園
例如在無遮擋情況下(往往會有多條等價路徑),而我們希望起點到終點實際只取其中一條路徑,而該路徑外其它節點可以沒必要放入openlist(不希望加入沒必要的鄰居)。
其次我們還希望直線方向上中途的點不用放入openlist,如果只放入每段直線子路徑的起點和終點,那openlist又可以少放很多沒必要的節點:
可以看到 JPS 演算法搜到的節點總是“跳躍性”的,這是因為這些關鍵性的節點都是需要改變行走方向的拐點,因此這也是 Jump Point 命名的來歷。
在介紹JPS等演算法具體實現前,我們必須先掌握下面的概念。
強迫鄰居(Forced Neighbour)
強迫鄰居:節點 x 的8個鄰居中有障礙,且 x 的父節點 p 經過x 到達 n 的距離代價比不經過 x 到達的 n 的任意路徑的距離代價小,則稱 n 是 x 的強迫鄰居。
看定義也許十分晦澀難懂。直觀來說,實際就是因為前進方向(父節點到 x 節點的方向為前進方向)的某一邊的靠後位置有障礙物,因此想要到該邊靠前的空位有最短的路徑,就必須得經過過 x 節點。
可能的情況見圖示,黑色為障礙,紅圈即為強迫鄰居:
(左圖為直線方向情況下的強迫鄰居,右圖為斜方向情況下的強迫鄰居)
跳點(Jump Point)
跳點:當前點 x 滿足以下三個條件之一:
- 節點 x 是起點/終點。
- 節點 x 至少有一個強迫鄰居。
- 如果父節點在斜方向(意味著這是斜向搜尋),節點x的水平或垂直方向上有滿足條件a,b的點。
節點y的水平或垂直方向是斜向向量的拆解,比如向量d=(1,1),那麼水平方向則是(1,0),並不會往左搜尋,只會看右邊,如果向量d=(-1,-1),那麼水平方向是(-1,0),只會搜尋左邊,不看右邊,其他同理。
下圖舉個例子,由於黃色節點的父節點是在斜方向,其對應分解成向上和向右兩個方向,因為在右方向發現一個藍色跳點,因此黃色節點也應被判斷為跳點:
JPS 尋路演算法(Jump Point Search)
實現原理
JPS 演算法和A* 演算法非常相似,步驟大概如下:
- openlist取一個權值最低的節點,然後開始搜尋。(這些和A*是一樣的)
- 搜尋時,先進行 直線搜尋(4/8個方向,跳躍搜尋),然後再 斜向搜尋(4個方向,只搜尋一步)。如果期間某個方向搜尋到跳點或者碰到障礙(或邊界),則當前方向完成搜尋,若有搜到跳點就新增進openlist。
跳躍搜尋是指沿直線方向一直搜下去(可能會搜到很多格),直到搜到跳點或者障礙(邊界)。一開始從起點搜尋,會有4個直線方向(上下左右),要是4個斜方向都前進了一步,此時直線方向會有8個。
- 若斜方向沒完成搜尋,則斜方向前進一步,重複上述過程。
因為直線方向是跳躍式搜尋,所以總是能完成搜尋。
- 若所有方向已完成搜尋,則認為當前節點搜尋完畢,將當前節點移除於openlist,加入closelist。
- 重複取openlist權值最低節點搜尋,直到openlist為空或者找到終點。
下面結合圖片更好說明過程2和3:首先我們從openlist取出綠色的節點,作為搜尋的開始,先進行直線搜尋,再斜向搜尋,沒有找到任何跳點。
斜方向前進一步後,重複直線搜尋和斜向搜尋過程,仍沒發現跳點。
斜方向前進兩步後,重複直線搜尋和斜向搜尋過程,仍沒發現跳點。
斜方向前進了三步後(假設當前位置為 x),在水平直線搜尋上發現了一個跳點(紫色節點為強迫鄰居)。
於是 x 也被判斷為跳點,新增進openlist。斜方向結束,綠色節點的搜尋過程也就此結束,被移除於openlist,放入closelist。
示例過程
下面展示JPS演算法更加完整的過程:
假設起點為綠色節點,終點為紅色節點。
重複直線搜尋和斜向搜尋過程,斜方向前進了3步。在第3步判斷出黃色節點為跳點(依據是水平方向有其它跳點),將黃色跳點放入openlist,然後斜方向搜尋完成,綠色節點移除於openlist,放入closelist。
對openlist下一個權值最低的節點(即黃色節點)開啟搜尋,在直線方向上發現了藍色節點為跳點(依據是紫色節點為強迫鄰居),類似地,放入openlist。
由於斜方向還沒結束,繼續前進一步。最後一次直線搜尋和斜向搜尋都碰到了邊界,因此黃色節點搜尋完成,移除於openlist,放入closelist。
對openlist下一個權值最低的節點(原為藍色節點,下圖變為黃色節點)開啟搜尋,直線搜尋碰到邊界,斜向搜尋無果。斜方繼續前進一步,仍然直線搜尋碰到邊界,斜向搜尋無果。
由於斜方向還沒結束,繼續前進一步。
最終在直線方向上發現了紅色節點為跳點,因此藍色節點先被判斷為跳點,只新增藍色節點進openlist。斜方向完成,黃色節點搜尋完成。
最後openlist取出的藍色節點開啟搜尋,在水平方向上發現紅色節點,判斷為終點,演算法完成。
回憶起跳點的第三個判斷條件(如果父節點在斜方向,節點x的水平或垂直方向上有滿足條件a,b的點),會發現這個條件判斷是最複雜的。在尋路過程中,它使尋路多次在水平節點上搜到跳點,也只能先新增它本身。其次,這也是演算法中需要使用到遞迴的地方,是JPS演算法效能瓶頸所在。
JPS+(Jump Point Search Plus)
JPS+ 本質上也是 JPS尋路,只是加上了預處理來改進,從而使尋路更加快速。
預處理
我們首先對地圖每個節點進行跳點判斷,找出所有主要跳點:
然後對每個節點進行跳點的直線可達性判斷,並記錄好跳點直線可達性:
若可達還需記錄號跳點直線距離:
類似地,我們對每個節點進行跳點斜向距離的記錄:
剩餘各個方向如果不可到達跳點的資料記為0或負數距離。如果在對應的方向上移動1步後碰到障礙(或邊界)則記為0,如果移動n+1步後會碰到障礙(或邊界)的資料記為負數距離-n
最後每個節點的8個方向都記錄完畢,我們便完成了JPS+的預處理過程:
以上預處理過程需要有一個資料結構儲存地圖上每個格子8個方向距離碰撞或跳點的距離。
示例過程
做好了地圖的預處理之後,我們就可以使用JPS+演算法了。大致思路與JPS演算法相同,不過這次有了預處理的資料,我們可以更快的進行直線搜尋和斜向搜尋。
在某個搜尋方向上有:
- 對於正數距離 n(意味著距離跳點 n 格),我們可以直接將n步遠的節點作為跳點新增進openlist
- 對於0距離(意味著一步都不可移動),我們無需在該方向搜尋;
- 對於負數距離 -n(意味著距離邊界或障礙 n 格),我們直接將n步遠的節點進行一次跳點判斷(有可能滿足跳點的第三條件,不過得益於預處理的資料,這步也可以很快完成)。
如下圖示,起始節點通過已記錄的向上距離,直接將3步遠的跳點新增進openlist,而不再像以前需要迭代三步(還每步都要判斷是否跳點):
其它過程也是類似的:
總結
可以看到 JPS/JPS+ 演算法裡只有跳點才會被加入openlist裡,排除了大量不必要的點,最後找出來的最短路徑也是由跳點組成。這也是 JPS/JPS+ 高效的主要原因。
JPS :
- 絕大部分地圖,使用 JPS 演算法都會比 A* 演算法更快,記憶體佔用也更小(openlist裡節點少了很多)。
- JPS 在跳點判斷上,要儘可能避免遞迴的深度過大(或者期待一下以後出現避免遞迴的演算法),否則在超大型的地圖裡遞迴判斷跳點可能會造成災難。
- JPS 也可以用於動態變化的地圖,只是每次地圖改變都需要再進行一次 JPS 搜尋。
- JPS 天生擁有合併節點(亦或者說是在一條直線裡移除中間不必要節點)的功能,但是仍存在一些可以繼續合併的地方。
- JPS 只適用於 網格(grid)節點型別,不支援 Navmesh 或者路徑點(Way Point)。
JPS+ :
- JPS+ 相比 JPS 演算法又是更快上一個檔次(特別是避免了過多層遞迴判斷跳點),記憶體佔用則是每個格子需要額外記錄8個方向的距離資料。
- JPS+ 演算法由於包含預處理過程,這讓它面對動態變化的地圖有天生的劣勢(幾乎是不可以接受動態地圖的),因此更適合用於靜態地圖。
- JPS+ 預處理的複雜度為 \(O(n)\) ,n 代表地圖格子數。
演算法 | 效能 | 記憶體佔用 | 支援動態地圖 | 預處理 | 支援節點型別 |
---|---|---|---|---|---|
A* | 中等 | 大 | 支援 | 無 | 網格、Navmesh、路徑點 |
JPS | 快 | 偏小 | 支援 | 無 | 網格 |
JPS+ | 非常快 | 中等 | 不支援 | 有,\(O(n)\) | 網格 |
綜上,JPS/JPS+ 是A*演算法的優秀替代者,絕大部分情況下更快和更小的記憶體佔用已經足夠誘人。在GDC 2015 關於 JPS+ 演算法的演講中,Steve Rabin 給出的資料甚至是比A* 演算法快70~350倍。
參考
[1] 從頭理解JPS尋路演算法 - 簡書 by ElephantKing
[2] JPS+: Over 100x Faster than A* | GDC 2015
[3] JPS+ with GoalBounding C++實現,和上面GDC2015的演講人是同一個人 Steve Rabin。
[4] 一個線上視覺化的JPS實現附說明 A Visual Explanation Of Jump Point Search
博主其它相關文章:
遊戲AI 系列文章 - KillerAery - 部落格園
遊戲AI之路徑規劃 - KillerAery - 部落格園