Jump Point Search (JPS) アルゴリズムは、A* アルゴリズムを改良したもので、大規模な地図での探索効率を向上させます。
A* アルゴリズムは、拡張するノードのすべての隣接ノードを考慮しますが、地図が大きい場合、openList 中のノード数が多くなり、効率が低下します。
JPS アルゴリズムは、直線的なパスの場合、中間ノードを探索に含めません。探索が必要なのは、道のりを変えるための「拐点」(jump point)のみです。
実装前のルール確認
- Forced Neighbors: ノード X の隣接ノード N が、X を通らないと到達できない場合、N は X の強制的な隣接ノードです。
- Jump Points:
- 起点または終点。
- 強制的な隣接ノードを有する。
- 斜方向からの探索において、水平または垂直方向に強制的な隣接ノードを有する。
斜方向探索とは、(1,1)、(-1,1)、(1,-1)、(-1,-1) の4つの方向を指します。
JPS アルゴリズムの仕組み
- openList を定義し、起点 S を openList に追加します。
- openList が空でない限り、最小の f-スコアを持つノード A を取り出し、A が終点である場合に成功します。
- 探索方向 dirList を決定します。起点の場合、8つの方向を探索します。
- 各方向 dir について、直線的に探索します。探索途中で jump point を発見した場合、そのノードを openList に追加します。
探索方向 dirList の決定方法:
if (A に親がない) {
dirList = {上、下、左、右、左上、右上、右下、左下};
} else {
dirList = {水平方向、垂直方向、斜方向};
// 親からの方向を分解し、dirList に追加
if (horizontalDir != 0) {
dirList.Add((horizontalDir, 0));
}
if (verticalDir != 0) {
dirList.Add((0, verticalDir));
}
if (horizontalDir != 0 かつ verticalDir != 0) {
dirList.Add((horizontalDir, verticalDir));
}
}
jump point を発見するための直線探索:
foreach (方向 dir in dirList) {
B = A
while (true) {
B += dir
if (B が jump point) {
B を openList に追加
break;
}
if (B が障害物または地図境界) {
break;
}
}
}
Python での JPS アルゴリズムの実装
以下に、JPS アルゴリズムを Python で実装した例を示します。
Node クラス:
class Node:
def __init__(self, x, y, parent=None):
self.x = x
self.y = y
self.parent = parent
self.g = 0
self.h = 0
self.f = 0
優先度キュー (openList) を使用します:
import heapq
def jps_search(grid, start, end):
open_list = []
heapq.heappush(open_list, (0, start))
closed_list = set()
while open_list:
current = heapq.heappop(open_list)[1]
if current == end:
return reconstruct_path(current)
closed_list.add((current.x, current.y))
# 探索方向を決定
if current.parent is None:
directions = [(-1, 0), (1, 0), (0, -1), (0, 1),
(-1, 1), (1, 1), (1, -1), (-1, -1)]
else:
dx = current.x - current.parent.x
dy = current.y - current.parent.y
directions = []
if dx != 0:
directions.append((dx, 0))
if dy != 0:
directions.append((0, dy))
if dx != 0 and dy != 0:
directions.append((dx, dy))
for dir in directions:
x, y = current.x, current.y
step = 1
while True:
x += dir[0]
y += dir[1]
if not is_valid(grid, x, y):
break
# jump point を検出
if is_jump_point(grid, x, y, current.parent):
# 前のノードを jump point として追加
x -= dir[0]
y -= dir[1]
next_node = Node(x, y, current)
next_node.g = current.g + step
next_node.h = heuristic(next_node, end)
next_node.f = next_node.g + next_node.h
if (next_node.x, next_node.y) not in closed_list:
heapq.heappush(open_list, (next_node.f, next_node))
break
step += 1
有効性チェック:
def is_valid(grid, x, y):
rows = len(grid)
cols = len(grid[0]) if rows > 0 else 0
if x < 0 or x >= rows or y < 0 or y >= cols:
return False
return grid[x][y] == 0 # 0 は通過可能、1 は障害物
Heuristic 関数:
def heuristic(node, end):
return abs(node.x - end.x) + abs(node.y - end.y)
jump point 検出:
def is_jump_point(grid, x, y, parent):
if parent is None:
return True # 起点は常に jump point
# 強制的な隣接ノードを検出
forced = get_forced_neighbors(grid, x, y, parent)
return len(forced) > 0
def get_forced_neighbors(grid, x, y, parent):
forced = []
dx = x - parent.x
dy = y - parent.y
# 水平方向の強制的な隣接ノードを検出
if dx != 0:
# 上方向
if is_valid(grid, x, y + 1):
forced.append((x, y + 1))
# 下方向
if is_valid(grid, x, y - 1):
forced.append((x, y - 1))
# 垂直方向の強制的な隣接ノードを検出
if dy != 0:
# 右方向
if is_valid(grid, x + 1, y):
forced.append((x + 1, y))
# 左方向
if is_valid(grid, x - 1, y):
forced.append((x - 1, y))
return forced
パス再構築:
def reconstruct_path(node):
path = []
while node is not None:
path.append((node.x, node.y))
node = node.parent
return path[::-1] # 順序を逆転
注意点
- 起点の場合、8つの方向を探索します。
- 親ノードがある場合、親からの方向を分解し、水平、垂直、斜方向を探索します。
- 直線探索中に jump point を発見した場合、そのノードを openList に追加します。
この実装は、大規模な地図での探索効率を向上させます。障害物がない直線状のパスは、少数の jump point で済みます。