从头理解JPS寻路算法

文末有新版描述

JPS(jump point search)概述

本文的思路受到博客:http://blog.sina.com.cn/s/blog_4a5c75d40102wo5l.html
和论文:http://www.doc88.com/p-6099778647749.html的启发和借鉴。
JPS(jump point search)算法实际上是对A寻路算法的一个改进,即在扩展搜索节点时,提出了更优化的策略,A在扩展节点时会把节点所有邻居都考虑进去,这样openlist中点的数量会很多,搜索效率较慢。JPS算法通过寻找跳点的方式,排除了大量不感兴趣的点,减少了openlist中搜索的点的数量,速度大大提高。

图例拆解JPS过程

自然邻居:如图所示,红色块是自然邻居。
被迫邻居:点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),只会搜索左边,不看右边,其他同理。

从头理解JPS寻路算法_第1张图片
红色块为自然邻居,黑色块为障碍物,绿色块为被迫邻居

水平(垂直)搜索:如图右边部分描述,点1和4如果经过x到达,还不如从点p(x)到达,所以不用考虑点1,4,同理继续向右搜索时,点2,5和点3,6,都是类似的情况,直到遇到黑色的障碍,没有发现感兴趣的点,x处水平方向搜索完成,垂直方向搜索类似。如图左边部分描述,在点x处向右搜索至点y时,点y有一个强迫邻居(点7),因此y是从点x处沿水平方向搜索的一个跳点,需要将y加入到openlist中。水平搜索结束。

从头理解JPS寻路算法_第2张图片
水平(垂直)搜索

对角搜索:斜向搜索时,需要先在当前点的水平和垂直方向搜索一下,看能否找到感兴趣的点,如果找到,则将当前点加到openlist,结束斜向搜索,如果找不到,则继续斜向多走一步,继续,直到找到感兴趣的点或者斜向方向遇到障碍。

从头理解JPS寻路算法_第3张图片
斜向搜索的两种情况

一条完整路线的搜索过程:

从头理解JPS寻路算法_第4张图片
完整搜索路线,起点S,终点T,黄色框是在过程中加入openlist的点,红色箭头尝试搜索返回失败,绿色箭头成功返回感兴趣点。

搜索算法:

Algorithm 1 Identify Successors

Require: x:current node, s:start, g:goal
1: successor(x) <- empty
2: neighbours(x) <- prune(x, neighbours(x))
3: for all n in neighbours(x) do
4:__n <- jump(x, direction(x,n),s,g)
5:__add n to successor(x)
6:return successor(x)

Algorithm 2 Function Jump

Require: x:initial node, d:direction, s:start, g:goal
1: n <- step(x, d)
2: if n is an obstacle or is outside the grid then
3:__return null
4: if n = g then
5: return n
6: if exists n' belongs to neighbours(n) s.t. n' is forced then
7:__return n
8: if d is diagonal then
9: __for i in {1,2} do
10: ____if jump(n,di,s,g) is not null then return n
11: return jump(n,d,s,g)

A*与JPS对比

下面两张对比图,摘自上面的论文,可以看出,JPS算法实在优秀。

从头理解JPS寻路算法_第5张图片
A*与JPS扩展节点对比
从头理解JPS寻路算法_第6张图片
各寻路算法路径长度和搜索时间

代码

github链接

2020-5-15 重写版本

  1. 强迫邻居(被迫邻居)定义
从头理解JPS寻路算法_第7张图片
自然邻居和强迫邻居

上图中红色块为节点x,基于前节点为P(x)的情况下的自然邻居节点。而被迫邻居则是图中绿色的方块(其对称情况未画出)。x被迫邻居包含三层隐藏含义:首先被迫邻居的位置是基于P(x)、x、阻挡块的相对关系定的,如第三个图所示,如果第三个图中,P(x)的位置在节点6的位置,那么绿色方块就不是x的被迫邻居了。其次,被迫邻居一点是考察非自然邻居的节点。最后被迫邻居一定是P(x)经过x到达才能取得最短路径的点。

注意:接下来的讨论都基于一个前提:斜向可以通过,即如上图4中,P(x)和x可以直达,x和绿色块可以直达。
有些算法或者游戏实践中,这种斜向穿过是不允许的,如P(x)->x,这好比人物穿过了墙角,在游戏内是不可以接受的,应该走P(x)->6->x。

  1. 跳点定义
    满足如下条件的点视为跳点:
    i. 节点y是起点或终点。
    ii. 节点y在当前搜索方向的前提下,有强迫邻居。
    iii. 当前搜索方向是斜向,且在y节点处,经过该斜向的水平分量或垂直分量方向移动可以找到跳点,那么当前节点y是跳点。

  2. 搜索规则
    i. 在搜索时,如果直线和斜向都能通过,先在直线方向搜索跳点,然后在斜向上搜索跳点。
    ii. n是x的邻居,若有从P(x)到n且不经过x的路径,且该路径小于等于从P(x)经过x到达n的路径的长度,那么走到x后下一个节点不会走到n。
    iii. 只有跳点才会被加入openlist里,最后找出来的最短路径也是由跳点组成。

一次完整的搜索过程

从头理解JPS寻路算法_第8张图片
JPS搜索过程

起点S,终点T,黑色块为阻挡黄色块为跳点,红色箭头是搜索方向,但是没有找到跳点,绿色箭头表示找到跳点。横坐标A-N,纵坐标1-10。

起始位置S,将A10加入openlist里,开始算法流程。
从openlist里取出代价最小的节点(现在为S),当前节点没有方向,所以需要搜索8个方向,只有右上方B9点是跳点。因为根据跳点定义iii,斜向搜索时,需要在两个分量方向查找跳点,右方是墙壁,略过,上方B8点有强迫邻居点C7,所以B8是跳点(根据跳点定义ii),所以B9是跳点。将B9加入openlist里。A10处理完后,将其从openlist中移除,加入closelist里。

从openlist中取出B9点,该点的父亲是S,所以搜索方向为右斜上,需要在右斜上,右方,上方搜索跳点,只有B8满足要求,将B8加入openlist。处理完B9点将其移到closelist。

从openlist取出B8点,该点搜索方向为上,B8有被邻居C7点,在斜上方搜索跳点,可以看出D8是C7的被迫邻居,所以C7是跳点,B8处继续向上搜索结束。

从openlist中取出C7点。需要搜索右上,右,上三个方向。水平搜索时发现被迫邻居D8,加入openlist。右上搜索时发现跳点G3(因其在上方搜索时发现具有被迫邻居节点的G2),将G3加入openlist。C7点处理结束。

取出G3点(为啥是G3,而不是D8点呢,因为从总代价来说G3比D8更小,总代价=已经走过的距离 + 估值,估值可采用曼哈顿距离或者高斯距离),需要搜索右上,右,上三个方向。向上搜索到跳点G2。

其余点路径在图上标注,就不再重复了。

实作中的搜索流程

(1) 若current方向是直线
i. 如果current左后方不可走且左方可走,则沿current左前方和左方寻找跳点。
ii. 如果current当前方向可走,则沿current方向寻找跳点。
iii. 如果current右后方不可走且右方可走,则沿current右前方和右方寻找跳点。
(2) 若current方向是斜线
i. 如果当前方向的水平分量可走,则沿current方向的水平分量方向寻找跳点。
ii. 如果当前方向可走,沿current方向寻找跳点。
iii. 如果当前方向的垂直分量可走,则沿current方向的垂直分量方向寻找跳点。

上述算法流程是建立在斜向不可穿过阻挡基础上。

你可能感兴趣的:(从头理解JPS寻路算法)