3. A*全局路径搜索
任务目标
在二维栅格地图上实现A*全局路径搜索算法,给定起点与终点坐标,输出从起点到终点的最短路径点序列,并在地图上可视化路径结果。
实现要点
A*算法通过维护开放列表(待扩展节点)与关闭列表(已扩展节点),以f(n) = g(n) + h(n)为优先级进行搜索。其中g(n)为从起点到当前节点的实际代价,h(n)为当前节点到终点的启发式估计代价(采用欧几里得距离)。算法保证在栅格地图上找到最短路径,前提是启发式函数满足可采纳性(不高估实际代价)。
完整代码
import heapq
import math
from typing import List, Tuple
# 栅格地图定义:0=可通行,1=障碍物
GRID = [
[0, 0, 0, 0, 1, 0, 0, 0],
[0, 1, 1, 0, 1, 0, 1, 0],
[0, 0, 0, 0, 0, 0, 1, 0],
[0, 1, 0, 1, 1, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 1, 0],
[0, 0, 0, 1, 0, 0, 0, 0],
]
START = (0, 0) # (row, col)
GOAL = (5, 7)
def heuristic(a: Tuple[int, int], b: Tuple[int, int]) -> float:
"""欧几里得距离启发式"""
return math.sqrt((a[0] - b[0])**2 + (a[1] - b[1])**2)
def get_neighbors(node: Tuple[int, int], grid: List[List[int]]) -> List[Tuple[int, int]]:
"""返回4邻域可通行邻居节点"""
rows, cols = len(grid), len(grid[0])
r, c = node
neighbors = []
for dr, dc in [(-1,0), (1,0), (0,-1), (0,1)]:
nr, nc = r + dr, c + dc
if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] == 0:
neighbors.append((nr, nc))
return neighbors
def a_star(grid: List[List[int]], start: Tuple[int, int], goal: Tuple[int, int]) -> List[Tuple[int, int]]:
"""A*主算法,返回路径点列表(含起点和终点);无路径时返回空列表"""
open_heap = [] # (f, g, node)
heapq.heappush(open_heap, (heuristic(start, goal), 0, start))
came_from = {} # 记录父节点用于回溯路径
g_score = {start: 0}
closed_set = set()
while open_heap:
_, current_g, current = heapq.heappop(open_heap)
if current == goal:
# 回溯路径
path = []
while current in came_from:
path.append(current)
current = came_from[current]
path.append(start)
path.reverse()
return path
if current in closed_set:
continue
closed_set.add(current)
for neighbor in get_neighbors(current, grid):
if neighbor in closed_set:
continue
tentative_g = current_g + 1 # 每步代价为1
if neighbor not in g_score or tentative_g < g_score[neighbor]:
g_score[neighbor] = tentative_g
f = tentative_g + heuristic(neighbor, goal)
heapq.heappush(open_heap, (f, tentative_g, neighbor))
came_from[neighbor] = current
return [] # 无可行路径
# 执行搜索
path = a_star(GRID, START, GOAL)
# 输出结果
if path:
print(f"找到路径,共 {len(path)} 个节点:")
print(" -> ".join([f"({r},{c})" for r, c in path]))
else:
print("未找到可行路径")
# 可视化路径(*表示路径,S起点,G终点,#障碍物)
visual = [['.' for _ in range(len(GRID[0]))] for _ in range(len(GRID))]
for r in range(len(GRID)):
for c in range(len(GRID[0])):
if GRID[r][c] == 1:
visual[r][c] = '#'
for r, c in path:
visual[r][c] = '*'
visual[START[0]][START[1]] = 'S'
visual[GOAL[0]][GOAL[1]] = 'G'
print("\n地图可视化:")
for row in visual:
print(' '.join(row))
运行与验证
运行方式:直接执行python a_star.py(将上述代码保存为a_star.py),无需额外安装依赖(仅使用Python标准库)。
预期输出:程序首先打印路径节点序列,随后打印带路径标记的地图。由于地图中起点(0,0)到终点(5,7)存在可行通道,预期输出路径长度为13个节点(含起点终点),路径节点坐标依次为:
(0,0) -> (0,1) -> (0,2) -> (0,3) -> (1,3) -> (2,3) -> (2,4) -> (2,5) -> (3,5) -> (4,5) -> (5,5) -> (5,6) -> (5,7)
验证标准:
- 路径首尾分别为
(0,0)和(5,7); - 路径中任意相邻节点在地图上为上下左右相邻关系;
- 路径中不包含任何障碍物格子(值为1的位置);
- 路径长度(节点数)为13,这是该地图上从起点到终点的最短路径长度(可通过BFS验证)。
常见问题
- 修改地图:直接编辑
GRID二维列表,将障碍物位置改为1即可测试不同场景。 - 无路径情况:若起点或终点被障碍物包围,算法返回空列表并打印"未找到可行路径"。
- 对角线移动:当前实现仅支持4邻域移动(上下左右),如需8邻域移动,在
get_neighbors中添加对角线方向并将单步代价改为math.sqrt(2)即可。