JPS(jump point search)寻路算法

JPS(jump point search)寻路算法

JPS(jump point search)跳跃点寻路算法是对AStar寻路算法的一个改进。

AStar 算法在扩展节点时会把所有相邻的节点考虑进去,当地图比较大时,openList 中的节点数量会很多,搜索效率较低。

在两点之间没有障碍物时,那么中间的节点对我们来说是没有用的,不希望添加到 openList
如下
在这里插入图片描述

可以看到JPS 算法不是每个节点都紧密连接的,而是跳跃性的或者说只有需要转弯的节点(也叫拐点)才会被考虑,所以叫跳跃点(Jump Point)。

在实现JPS前先了解它的规则
1.强迫邻居:节点X的邻居节点有障碍物,且X的父节点P经过X到达N的距离代价,比不经过X到大N的任一路径的距离代价都小,则称N是X的强迫邻居。
不知道说的什么意思,看下图
在这里插入图片描述
2.跳点(Jump Point):什么样的节点可以作为跳点
(1)节点 A 是起点、终点.
(2)节点A 至少有一个强迫邻居.
(3)父节点在斜方向(斜向搜索),节点A的水平或者垂直方向上有满足 (1)、(2) 的节点

(1)、(2)两条不需要解释,下面看下什么是斜向搜索,下面是一个方向为 (1, 1)斜向搜索
在搜索过程中它可以将水平和垂直方向两个分量,分解为一个方向为(1, 0)的水平搜索,一个方向为(0, 1)的垂直搜索
同理斜向有四种方向
左上 (-1, 1) ——>对应的水平 (-1, 0),垂直 ( 0, 1)
右上 ( 1, 1) ——>对应的水平 ( 1, 0),垂直 ( 0, 1)
右下 ( 1, -1) ——>对应的水平 ( 1, 0),垂直 ( 0, -1)
左下 (-1, -1) ——>对应的水平 (-1, 0),垂直 ( 0, -1)
在这里插入图片描述
如上所说(3)的情形即为如下
在这里插入图片描述

JSP寻路算法原理如下
JPS 算法和AStar算法非常相似,如果不了解AStar算法的可以先看下 AStar再看JPS会事半功倍。

一.定义开放列表 openList,将起点 S 加入到 openList
二.如果开放列表中节点数大于0个,则取出权值最低的节点 A,如果节点A 是终点则搜索成功返回。
先进行直线搜索 ( 上(0, 1)、下(0, -1)、 左(-1, 0)、右(1, 0))四个方向,根据情况判断需要搜索的方向放入dirList

dirList = {};
if(A没有父节点)
{
    dirList = {(0, 1),(0, -1),(-1, 0),(1, 0),
               左上(-1, 1), 右上(1, 1), 右下(1, -1), 左下(-1, -1)
               };
}
else
{
    父节点为 P
    PA = (horizontalDir,verticalDir)  // P 到 A 方向的 坐标偏移
    // 从父节点到 A 节点 方向分解的水平 horizontalDir、垂直方向verticalDir 
    // 如父节点 P 到节点 A 的方向 PA 斜向右上(1, 1)则 horizontalDir= 1、verticalDir = 1
    // 如父节点 P 到节点 A 的方向 PA 向右(1, 0)则 horizontalDir=1 、verticalDir= 0
    if(horizontalDir != 0)
    {
        dirList.Add((horizontalDir, 0));
    }
    
    if(verticalDir != (0, 0))
    {
        dirList.Add((0, verticalDir ));
    }
    
    if(horizontalDir  != 0 && verticalDir  != 0)
    {
        //斜向 PA(X,Y)搜索
        dirList.Add((horizontalDir, verticalDir  ));
    }
    
    foreach(Node N in A 的所有强制邻居)
    {
         // AN 节点 A 到其强制邻居节点的偏移,一定是斜向的
         dirList.Add(AN);
    }
}

foreach (方向 dir in dirList)
{
       节点B = A
       while(true)
       {
            B += dir //向前迈进一步
            if(B 是跳点)
            {
                给B赋权值并且将跳点加入 openList 
                break;
            }
            if(B是障碍物、地图边界)
            {
                break;
            }
       }
}

注意事项:
(1).如果节点A没有父方向P(起点)
则直线方向按照 (上下左右)四个方向, dirList = {上、下、左、右}
斜方向按照(左上、右上、右下、左下)四个方向搜索 dirList = {左上、右上、右下、左下}
(2).如果节点A有父方向P
则 PA=(X,Y)
将PA分解为水平 horizontalDir=(X,0),垂直 verticalDir=(0,Y)

还是先考虑水平和垂直的搜索方向,dirList = {}
如果 horizontalDir=(X,0) != (0, 0) 即 X != 0 则 将 horizontalDir 加入到 dirList
如果 verticalDir=(0,Y) !=(0, 0) 即 Y != 0 则 将 verticalDir 加入到 dirList
直线方向搜索 dirList 中的方向

然后是斜向 dirList = {}
如果 PA=(X,Y),X != 0 且 Y != 0, 则将 PA方向加入到 dirList
如果 A有强迫邻居 {N1, N2, N3…},则将 AN1,AN2,AN3,。。。都加入到 dirList

三.回到步骤二继续搜索

下面以一个例子演示JPS搜索的全过程
讲解在图下面,先看图,在看图下边的讲解
在这里插入图片描述

看上图,S为起点以绿色格子表示,E为终点以红色表示,棕色节点为障碍物不可通过
1.开始将 S加入到openList
2.从openList 中取出权值最小的节点,此时是S节点,S没有父方向,则开始搜索 (左、上、右、下)四个直线方向,左A1、上B1、下D1处遇到地图边界,C1处遇到障碍物。直线搜索结束
在这里插入图片描述
3.看上图
开始向S左上(-1, 1) SE1方向的斜向搜索
SE1(-1, 1)可以分解为水平向左 (-1, 0)和垂直向上(0, 1)两个方向
则SE1斜向搜索需要搜索 水平向左 (-1, 0)、垂直向上(0, 1)和斜向左上(-1, 1) 三个方向
E1向左搜索到达边界,向上搜索到达边界,E1不是跳点,斜向迈进一步到达E2
E2向左搜索到达边界,向上搜索到达边界,E2不是跳点,且已到达边界,结束
在这里插入图片描述
4.看上图
开始向S右上(1, 1) SF1方向斜向搜索
SF1(1, 1)可以分解为水平向右(1, 0)和垂直向上(0, 1)两个方向
则SF1斜向搜索需要搜索 水平向右(1, 0),垂直向上(0, 1)和 斜向右上(1, 1)三个方向
F1 向右搜索到障碍物,向上搜索到边界, F1不是跳点,斜向迈进一步到达F2
F2 向右搜索到障碍物,向上搜索到边界, F2不是跳点,斜向迈进一步到达F3
F3 向右搜索到障碍物,向上搜索到边界, F3不是跳点,斜向迈进一步到达F4
F4 向右发现节点M1 为 J2 的强迫邻居,则 F4为跳点,将 F4加入到 openList,将F4节点标记为蓝色,便于区分
由于 F4为 SF1发起的斜向搜索,所以是在 SF1斜向搜索的时候发现了跳点 F4,则不再向上搜索了,也不再继续斜向搜索,SF1斜向搜索停止
右上搜索结束
在这里插入图片描述
5.看上图
开始向S右下(1, -1) SG1 方向斜向搜索
SG1(1, -1)可以分解为 水平向右 (1, 0)和垂直向下(0, -1)
则SG1斜向搜索需要搜索 水平向右 (1, 0),垂直向下(0, -1)和斜向右下(1, -1)三个方向
G1 向右搜索到障碍物,向下搜索到边界, G1不是跳点,斜向迈进一步到达 G2
G2 向右搜索到障碍物, 向下搜索到边界, G2不是跳点,斜向迈进一步到达 G3
G3 向右发现节点J3 为 J1 的强迫邻居,则 G3 为跳点,将 G3加入到 openList,将G3节点标记为蓝色
由于G3为 SG1 发起的斜向搜索,所以是在 SG1斜向搜索的时候发现了跳点G3,则不再继续向下搜索了,也不在继续斜向搜索,SG1斜向搜索停止
右下搜索结束
在这里插入图片描述
6. 看上图
开始向S左下(-1, -1) SH1 方向斜向搜索
SH1(-1, -1)可以分解为 水平向左(-1, 0) 和 垂直向下(0, -1) 两个方向
则SH1斜向搜索需要搜搜 水平向左(-1, 0) ,垂直向下(0, -1)和斜向左下(-1, -1)三个方向
H1 向左搜索到障碍物,向下搜索到边界,H1不是跳点,斜向迈进一步到达 H2
H2 向左搜索到障碍物,向下搜索到边界,H2到达边界了结束
左下搜索结束
此时围绕 S节点的搜索结束,将S节点放入 closedList
在这里插入图片描述
7.看上图
取出 openList 中权值最小的节点此时openList ={G3, F4}, 且G3权值最小,则取出G3,将G3标记为绿色
G3的父方向时 S,则 SG3 方向为右下(1, -1)
SG3可以分解出水平向右 (1, 0),垂直向下 (0, -1) 两个方向
则SG3斜向搜索需要搜索 水平向右 (1, 0),垂直向下 (0, -1)和斜向右下(1, -1)三个方向

G3向右搜索发现节点J3是 J1 的强迫邻居,则J1是跳点,将节点J3加入到J1的强迫邻居列表中,将 J1添加到 openList,将 J1标记为蓝色
由于不是斜向搜索发现的跳点,则继续
G3向下搜索到边界,斜向迈进一步到达 G4
G4向右搜索到达边界,向下搜索到达边界,G4不是跳点,则斜向迈进一步到达G5
G5向右搜索到达边界,向下搜索到达边界,G5到达边界,则搜索结束。
将G3加入到 closedList 中
在这里插入图片描述
8看上图
取出openList 中权值最小的节点此时 openList ={F4, J1},F4权值最小,将F4取出,将F4标记为绿色
F4的父方向是S,则SF4方向为右上(1, 1)
SF4可以分解为 水平向右(1, 0)和垂直向上(0, 1)两个方向
则SF4斜向搜索需要搜索的方向为 水平向右(1, 0)、垂直向上(0, 1)和斜向右上(1, 1)三个方向

F4向右搜索发现 节点M1 是节点 J2的强迫邻居,则J2是跳点,将节点M1加入到J2的强迫邻居列表中,将 J2加入到 openList,并将 J2标记为蓝色
由于不是斜向搜索发现的跳点,则继续
F4向上搜索到达边界,斜向迈进一步到达F5
F5向右搜索到达边界,F5向上搜索到达边界,F5不是跳点,则斜向迈进一步到达F6
F6向右搜索发现节点O1是K1的强迫邻居,则F6是跳点,则将 F6加入到 openList,将F6标记为蓝色,
由于F6为 SF4 发起的斜向搜索,所以是在 SF4斜向搜索的时候发现了跳点F6,则不再继续向下搜索了,也不在继续斜向搜索,SF4斜向搜索停止
将F4加入到 closedList
在这里插入图片描述
9.看上图
取出openList 中权值最小的节点此时 openList={J1, J2,F6},节点 J1的权值最小,将 J1从openList取出,并标记为绿色
J1的父方向时 G3,则G3J1方向为向右(1, 0),
G3J1分解的水平方向(1, 0)和垂直反向(0,0),其中垂直方向(0,0)不满足垂直方向要求舍去
J1向右搜索到达边界

由于G3J1方向为水平方向,没有斜向,则不搜索斜向
但是J1有一个强迫邻居节点J3
则需要搜索斜向右上 J1J3=(1,1),发现节点P1是J3强迫邻居,则J3是跳点,将 P1加入到J3的强迫邻居中,将 J3加入到 openList并且标记为蓝色
将J3加入到 closedList
搜索结束
在这里插入图片描述

10看上图
从openList中取出权值最小的节点,此时 openList ={J3, F6, J2}, 节点F6权值最小,则取出F6,并将F6标记为绿色
F6父方向是 F4,则F4K6方向为斜向右上 (1, 1)
F4F6分解为水平向右(1, 0)和垂直向上(0, 1)两个方向
则F4F6斜向搜索需要搜索 水平向右(1, 0),垂直向上(0, 1)和斜向右上(1,1)三个方向
F6向右搜索发现节点O1为 K1 的强迫邻居,K1为跳点,将O1加入到K1的强迫邻居中,将 K1加入到openList,并将K1标记为蓝色
不是斜向搜索到的跳点,继续
F6向上搜索到达边界,斜向迈进一步到达F7
F7向右搜索到达障碍物,向上搜索到达边界,
将F6加入到 closedList
搜索结束
在这里插入图片描述

11看上图
从openList中取出权值最小的节点,此时 openList ={K1, J3, J2}, 节点K1权值最小,则取出K1,并将K1标记为绿色
K1父方向是 F6,则F6K1方向为水平向右(1, 0)
F6K1分解为水平向右(1, 0)和垂直(0, 0)两个方向,且垂直方向(0,0)不满足要求
K1向右搜索到边界

F6K1为水平向右,没有斜向,则不搜索斜向
但是 K1有一个强迫邻居O1,则搜索斜向右上 K1O1=(1,1)
K1O1分解为水平向右(1, 0)和垂直向上(0, 1)
O1向右搜索到边界,向上搜索到边界,O1不是跳点,则斜向迈进一步到达 O2
O2向右搜索到边界,向上搜索到边界,O2到达边界,
将K1加入到 closedList
搜索结束
在这里插入图片描述

12看上图
从openList中取出权值最小的节点,此时 openList ={ J2,J3}, 节点J2权值最小,则取出J2,并将J2标记为绿色
J2父方向是 F4,则F4J2方向为水平向右 (1, 0)
F4J2分解为水平向右(1, 0)和垂直(0, 0)两个方向,且垂直方向(0,0)不满足要求
J2向右搜索到边界

F4J2为水平向右,没有斜向,不需要斜向搜索
但是J2有一个强迫邻居M1,则想 J2M1斜向右下搜索
M1 向右搜索到达边界,向下搜索发现 J3,J3已经在 openList,更新J3的权值,返回
发现 R1为M1的强迫邻居,M1为跳点,将R1加入到 M1的强迫邻居,M1加入到openList,并将M1标记为蓝色
将J2加入到closedList
搜索结束

在这里插入图片描述
13看上图
从openList中取出权值最小的节点,此时 openList ={ J3,M1}, 节点J3权值最小,则取出J3,并将J3标记为绿色
J3父方向是 J1,则J1F3方向为斜向右上 (1, 1)
J1J3分解为水平向右(1, 0)和垂直(0, 1)两个方向
J3向右索索到边界,向上搜索到M1,M1已经在openList中,如果从J3到M1 的权值比 M1当前权值更小,则更新M1的权值,斜向迈进一步到达N1
N1向右搜索到边界,向上搜索到边界,斜向迈进一步到达N2
N2向右搜索到边界,向上搜索到边界,斜向迈进一步到达N3
。。。直到到达边界

J3还有一个强迫邻居P1,斜向左上J3P1=(-1,1)
J3P1 分解为水平向左(-1,0)和垂直向上(0, 1)
P1向左搜索到障碍物,向上搜索到障碍物,斜向迈进一步到达P2
P2向左搜索到 终点E,终点是跳点,则将 P2加入到openList中
将J3加入到closedList

到这里已经触摸到终点E了,接下来再将 P2取出来就能搜索到终点E 了,不再说明了,搜索成功结束

下面是一个Unity实现的搜索展示动画
在这里插入图片描述
刚开始加入到openList的节点显示蓝色小球,当从openList中取出来时显示绿色小球

此处是代码实现连接,Unity辅助展示,核心逻辑与引擎无关、与语言无关

下面是一个寻路搜索展示网页的搜索展示动画
在这里插入图片描述
网页链接

补充:
代码 JPSTool 判断强制邻居部分,的计算逻辑说明

判断水平、垂直方向(水平向右、水平向左、竖直向上、竖直向下)的强制邻居

CheckHVForceNeighbour

判断斜向(左上、左下、右上、右下)的强制邻居

CheckDiagonalForceNeighbour

先来看水平方向逻辑
在这里插入图片描述
先看前置接点P和当前节点X 处于横向水平关系时
从上图可看出从 PX水平方向X 的强制邻居有分别是 N1、N2
组成部分是:
(1)P、X、O1、N1
(2)P、X、O2、N2

已知:P、X 坐标
如何求得 O1、N1、O2、N2 的坐标?
P 指向 X 的方向 dir = (1, 0)
(1.1)公式1:
O1坐标 = X + (Abs(dir.Y) * 1, Abs(dir.X) * 1)
O1坐标 = X + (0, 1)

(1.2)公式2:
N1 坐标 = O1坐标 + dir
N1 坐标 = O1 坐标 + (1, 0)

(2.1)公式3:
O2 坐标 = X + (Abs(dir.Y) * -1, Abs(dir.X) * -1)
O2 坐标 = X + (0, -1)
(2.2)公式4:
N2 坐标 = O2 坐标 + dir
N2 坐标 = O2 坐标 + (1, 0)

同理水平向左、竖直向上、竖直向下 也都是使用 公式1、公式2、公式3、公式4
公式1公式3的区别在于
公式1 乘以 1 -> (Abs(dir.Y) * 1, Abs(dir.X) * 1)
公式3 乘以 -1 -> (Abs(dir.Y) * -1, Abs(dir.X) * -1)

公式2 和 公式4 相同

再来看斜向逻辑
在这里插入图片描述
先看前置接点P和当前节点X 处于斜向关系
从上图可看出从 PX斜向左上X 的强制邻居有分别是 N1、N2
组成部分是:
(1)P、X、O1、N1
(2)P、X、O2、N2

已知:P、X 坐标
如何求得 O1、N1、O2、N2 的坐标?
P 指向 X 的方向 dir = (-1, 1)
(1.1)公式1:
O1坐标 = X + (dir.X, 0)
O1坐标 = X + (-1, 0)

(1.2)公式2:
N1 坐标 = O1 坐标 + (dir.X, 0)
N1 坐标 = O1 坐标 + (-1, 0)

(2.1)公式3:
O2 坐标 = X + (0, dir.Y)
O2 坐标 = X + (0, 1)

(1.2)公式4:
N2 坐标 = O2 坐标 + (0, dir.Y)
N2 坐标 = O2 坐标 + (0, 1)

同理水平向左、竖直向上、竖直向下 也都是使用 公式1、公式2、公式3、公式4

如有错误之处请指出,感谢

  • 44
    点赞
  • 162
    收藏
    觉得还不错? 一键收藏
  • 25
    评论
Jump Point Search算法是一种近年来提出的新型寻路算法,能够在路网数据中快速跳过大量的不可行区域,极大地提高了寻路效率。以下是一个简单的C++实现示例: ```cpp #include <iostream> #include <vector> #include <queue> #include <cmath> using namespace std; const int MAXN = 100; const int INF = 0x3f3f3f3f; int n, m; // 路网数据的行列数 int sx, sy, ex, ey; // 起点和终点的坐标 int map[MAXN][MAXN]; // 路网数据 int dis[MAXN][MAXN]; // 距离起点的距离 bool vis[MAXN][MAXN]; // 是否已经访问过 int dir[8][2] = {{0,1},{0,-1},{1,0},{-1,0},{1,1},{1,-1},{-1,1},{-1,-1}}; // 方向数组 struct Node { int x, y, f, g, h; // 坐标、f值、g值、h值 bool operator<(const Node& a) const { return f > a.f; // 优先队列为小根堆,f值小的优先级高 } }; bool is_valid(int x, int y) { return x >= 0 && x < n && y >= 0 && y < m && map[x][y] == 0; } vector<pair<int, int>> get_successors(int x, int y, int dx, int dy) { vector<pair<int, int>> successors; if (dx != 0 && dy != 0) { if (is_valid(x + dx, y + dy) && (!is_valid(x + dx, y) || !is_valid(x, y + dy))) { successors.push_back({x + dx, y + dy}); } if (is_valid(x + dx, y) && !is_valid(x, y + dy)) { successors.push_back({x + dx, y}); successors.push_back({x + dx, y + dy}); } if (is_valid(x, y + dy) && !is_valid(x + dx, y)) { successors.push_back({x, y + dy}); successors.push_back({x + dx, y + dy}); } } else { if (dx == 0) { if (is_valid(x, y + dy)) { successors.push_back({x, y + dy}); if (!is_valid(x + 1, y)) successors.push_back({x + 1, y + dy}); if (!is_valid(x - 1, y)) successors.push_back({x - 1, y + dy}); } } else { if (is_valid(x + dx, y)) { successors.push_back({x + dx, y}); if (!is_valid(x, y + 1)) successors.push_back({x + dx, y + 1}); if (!is_valid(x, y - 1)) successors.push_back({x + dx, y - 1}); } } } return successors; } void jump(int x, int y, int dx, int dy) { int nx = x + dx, ny = y + dy; if (!is_valid(nx, ny)) return; if (nx == ex && ny == ey) { dis[nx][ny] = dis[x][y] + 1; return; } if (vis[nx][ny]) return; vis[nx][ny] = true; int g = dis[x][y] + 1; int h = abs(nx - ex) + abs(ny - ey); int f = g + h; priority_queue<Node> pq; pq.push({nx, ny, f, g, h}); while (!pq.empty()) { Node u = pq.top(); pq.pop(); int x = u.x, y = u.y; if (vis[x][y]) continue; vis[x][y] = true; dis[x][y] = u.g; vector<pair<int, int>> successors = get_successors(x, y, dx, dy); for (auto& s : successors) { int nx = s.first, ny = s.second; int g = dis[x][y] + 1; int h = abs(nx - ex) + abs(ny - ey); int f = g + h; pq.push({nx, ny, f, g, h}); } } } void JPS() { memset(dis, INF, sizeof(dis)); memset(vis, false, sizeof(vis)); dis[sx][sy] = 0; vis[sx][sy] = true; priority_queue<Node> pq; pq.push({sx, sy, abs(ex - sx) + abs(ey - sy), 0, abs(ex - sx) + abs(ey - sy)}); while (!pq.empty()) { Node u = pq.top(); pq.pop(); int x = u.x, y = u.y; if (x == ex && y == ey) return; for (int i = 0; i < 8; i++) { int dx = dir[i][0], dy = dir[i][1]; if ((dx == 0 || dy == 0) || (is_valid(x + dx, y) && is_valid(x, y + dy))) { jump(x, y, dx, dy); } } } } int main() { cin >> n >> m >> sx >> sy >> ex >> ey; for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { cin >> map[i][j]; } } JPS(); cout << dis[ex][ey] << endl; return 0; } ``` 这段代码实现了Jump Point Search算法的基本框架,包括预处理跳跃点、递归跳跃、状态转移和优先队列的使用。需要注意的是,Jump Point Search算法的预处理过程比较耗时,因此它更适用于需要多次寻路的场景。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 25
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值