搜索算法--Jump Point Search

编者:Tony

日期:2021-12-30


说明:本文主要介绍JPS算法的原理

一、背景

JPS(jump point search)算法实际上是对A* 寻路算法的一个改进,因此在阅读本文之前需要先了解A*算法。A* 算法在扩展节点时会把节点所有邻居都考虑进去,这样openlist中点的数量会很多,搜索效率较慢。

在无遮挡情况下(往往会有多条等价路径),而我们希望起点到终点实际只取其中一条路径,而该路径外其它节点可以没必要放入openlist(不希望加入没必要的邻居)。

 

其次我们还希望直线方向上中途的点不用放入openlist,如果只放入每段直线子路径的起点和终点,那openlist又可以少放很多没必要的节点:

 

可以看到 JPS 算法搜到的节点总是“跳跃性”的,这是因为这些关键性的节点都是需要改变行走方向的拐点,因此这也是 Jump Point 命名的来历。

二、概念

自然邻居:如图所示,红色块是自然邻居。

被迫邻居:点x的8个邻居中有障碍,n不是x的自然邻居,且x的父亲节点p(x)经过x到达n的距离代价比不经过x到达的n的任意路径的距离代价小,则称n是x的被迫邻居。这其中隐含了对p(x)的描述,最完整的表述应该是,n是x在x的父亲节点是p(x)的情况下的被迫邻居。见图示。

跳点:基于点x(当前点为x),且搜索方向为d(斜向或水平或垂直),点y满足一下三个条件之一,那么y就是跳点。
a.节点y是终点,那么节点y是跳点。
b.节点y至少有一个被迫邻居,那么节点y是跳点。
c.如果d是斜向搜索,如果节点y的水平或垂直方向上有满足条件a,b的点,那么节点y是跳点。注:节点y的水平或垂直方向是斜向向量的拆解,比如向量d=(1,1),那么水平方向则是(1,0),并不会往左搜索,只会看右边,如果向量d=(-1,-1),那么水平方向是(-1,0),只会搜索左边,不看右边,其他同理。

红色块为自然邻居,黑色块为障碍物,绿色块为被迫邻居

黑色色块表示障碍物,橙色色块表示当前节点(current),绿色色块表示上一点(Parent),而黄色色块就是current节点的Forced Neighbor。从parent节点水平跳跃到current节点,这时current节点上方被障碍物挡住了,使路径parentcurrentForced Neighbor成为了parent节点和Forced Neighbor节点间的最短路径,current节点也就成为了从parent节点到Forced Neighbor节点的跳点

如果将current节点上方的障碍物去掉,那么还存在其他的最短路径,如下图,这时黄色色块就不能称作current节点的Forced Neighbor了。

 上文中提到从parent节点到Current节点的过程称为跳跃(Jump),分为直线跳跃(Jumping Straight)对角线跳跃(Jumping Diagonally),直线跳跃又分为水平跳跃和垂直跳跃,如下图,橙色箭头表示直线跳跃,紫色箭头表示对角线跳跃。一般情况都是先直线跳跃,再进行对角线跳跃。在进行直线跳跃时,如果跳跃到障碍物或者边界,则返回parent进行对角线跳跃;当对角线跳跃后的节点是障碍物或者边界时,则停止跳跃。

所以定义:具有强迫邻居的节点就是跳点。当然这个定义并不严格,如下图,绿色的node就是从openlist中弹出的节点,从node进行对角线跳跃到蓝色的parent节点,再由parent直线跳跃至current,发现current具有一个Forced Neighbor,所以current是一个跳点,但是从node到current不能直接进行跳跃,而是要经过parent,所以parent也是一个跳点。所以继续定义:如果节点x由前一个节点经过对角线跳跃得到,同时从x进行直线跳跃得到的current节点是一个跳点,那么节点x也是跳点

三、算法原理

  • 水平搜索

 

 

  • 斜向搜索

  • 原理方法

JPS 算法和A* 算法非常相似,步骤大概如下:

  1. openlist取一个权值最低的节点,然后开始搜索。(这些和A*是一样的)
  2. 搜索时,先进行 直线搜索(4/8个方向,跳跃搜索),然后再 斜向搜索(4个方向,只搜索一步)。如果期间某个方向搜索到跳点或者碰到障碍(或边界),则当前方向完成搜索,若有搜到跳点就添加进openlist。
跳跃搜索是指沿直线方向一直搜下去(可能会搜到很多格),直到搜到跳点或者障碍(边界)。一开始从起点搜索,会有4个直线方向(上下左右),要是4个斜方向都前进了一步,此时直线方向会有8个。
  1. 若斜方向没完成搜索,则斜方向前进一步,重复上述过程。
因为直线方向是跳跃式搜索,所以总是能完成搜索。
  1. 若所有方向已完成搜索,则认为当前节点搜索完毕,将当前节点移除于openlist,加入closelist。
  2. 重复取openlist权值最低节点搜索,直到openlist为空或者找到终点。

下面结合图片更好说明过程2和3:首先我们从openlist取出绿色的节点,作为搜索的开始,先进行直线搜索,再斜向搜索,没有找到任何跳点。

四、实验结果

  • 斜向搜索没有遇到障碍物

 

  • 斜向搜索遇到障碍物

  • 场景--200 * 400 大小的地图

  • 各寻路算法性能数据对比参考

五、拓展JPS+

  • 原理方法

JPS+(Jump Point Search Plus) 本质上也是 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倍。

六、参考

猜你喜欢

转载自blog.csdn.net/fightingTony/article/details/122251631
今日推荐