最优路径-A*算法(A-Star)
A*算法(A-Star)
算法概述
A* 算法(A-Star Algorithm)是由Peter Hart、Nils Nilsson和Bertram Raphael于1968年提出的一种启发式搜索算法,用于在状态空间中寻找从起始状态到目标状态的最优路径。A* 算法结合了广度优先搜索的完备性和启发式搜索的效率,在路径规划、游戏AI等领域有广泛应用。
算法原理
A*算法的核心思想是通过评估函数来指导搜索方向,平衡已探索路径的成本和预估剩余路径的成本:
- 评估函数:f(n)=g(n)+h(n)f(n) = g(n) + h(n)f(n)=g(n)+h(n)
- 已知成本:g(n)g(n)g(n) 表示从起始节点到当前节点 nnn 的实际成本
- 启发式成本:h(n)h(n)h(n) 表示从当前节点 nnn 到目标节点的预估成本
- 优先级选择:每次选择 f(n)f(n)f(n) 值最小的节点进行扩展
算法步骤
-
初始化:
- 将起始节点加入开放列表(open list)
- 设置起始节点的 g(n)=0g(n) = 0g(n)=0,h(n)h(n)h(n) 为启发式估计值
- 创建关闭列表(closed list)记录已访问节点
-
循环处理:
- 从开放列表中取出 f(n)f(n)f(n) 值最小的节点
- 如果该节点是目标节点,则路径找到,算法结束
- 将该节点移入关闭列表
- 对该节点的所有邻居进行扩展
-
邻居扩展:
- 对于每个邻居节点:
- 如果邻居在关闭列表中,跳过
- 计算新的 ggg 值:gnew=g(current)+cost(current,neighbor)g_{new} = g(current) + cost(current, neighbor)gnew=g(current)+cost(current,neighbor)
- 如果邻居不在开放列表中或新的 ggg 值更小:
- 更新邻居的 ggg 值和 fff 值
- 设置邻居的前驱节点为当前节点
- 如果邻居不在开放列表中,将其加入
- 对于每个邻居节点:
数学表达
设 G=(V,E)G = (V, E)G=(V,E) 为一个带权图,其中:
- VVV 是顶点集合
- EEE 是边集合
- w(u,v)w(u, v)w(u,v) 表示边 (u,v)(u, v)(u,v) 的权重
A*算法维护以下数据结构:
- g(n)g(n)g(n):从起始节点到节点 nnn 的实际成本
- h(n)h(n)h(n):从节点 nnn 到目标节点的启发式估计成本
- f(n)=g(n)+h(n)f(n) = g(n) + h(n)f(n)=g(n)+h(n):节点的评估函数值
算法的搜索过程满足:
f(n)=g(n)+h(n)≤g∗(n)+h∗(n)f(n) = g(n) + h(n) \leq g^*(n) + h^*(n)f(n)=g(n)+h(n)≤g∗(n)+h∗(n)
其中 g∗(n)g^*(n)g∗(n) 是从起始节点到节点 nnn 的最优成本,h∗(n)h^*(n)h∗(n) 是从节点 nnn 到目标节点的最优成本。
时间复杂度
- 最坏情况时间复杂度:O(bd)O(b^d)O(bd)
- 平均时间复杂度:取决于启发式函数的质量
- 空间复杂度:O(bd)O(b^d)O(bd)
其中:
- bbb 是分支因子
- ddd 是解的深度
算法特点
优点
- 能够找到最优解(在满足启发式条件的情况下)
- 搜索效率高,避免不必要的探索
- 可以通过调整启发式函数控制搜索行为
- 广泛应用于路径规划问题
缺点
- 需要设计合适的启发式函数
- 在最坏情况下可能需要大量内存
- 对于某些问题可能陷入局部最优
- 启发式函数的设计可能很复杂
应用场景
- 游戏AI:角色移动路径规划
- 导航系统:地图导航和路线规划
- 机器人路径规划:机器人运动轨迹规划
- 网络路由:网络数据包路由选择
- 自动寻路:各种自动寻路应用
伪代码
function AStar(start, goal, heuristic):
open_list ← new PriorityQueue()
closed_list ← new Set()
// 初始化起始节点
start.g ← 0
start.h ← heuristic(start, goal)
start.f ← start.g + start.h
start.parent ← null
open_list.add(start)
while open_list is not empty:
current ← open_list.remove()
if current == goal:
return reconstruct_path(current)
closed_list.add(current)
for each neighbor of current:
if neighbor in closed_list:
continue
tentative_g ← current.g + cost(current, neighbor)
if neighbor not in open_list or tentative_g < neighbor.g:
neighbor.parent ← current
neighbor.g ← tentative_g
neighbor.h ← heuristic(neighbor, goal)
neighbor.f ← neighbor.g + neighbor.h
if neighbor not in open_list:
open_list.add(neighbor)
return failure
实现示例
Python实现
import heapq
class Node:
def __init__(self, position, parent=None):
self.position = position
self.parent = parent
self.g = 0 # 从起始节点到当前节点的实际成本
self.h = 0 # 从当前节点到目标节点的启发式成本
self.f = 0 # 总评估成本
def __lt__(self, other):
return self.f < other.f
def a_star(grid, start, end, heuristic):
"""
A*算法实现
:param grid: 二维网格,0表示可通行,1表示障碍
:param start: 起始位置 (x, y)
:param end: 目标位置 (x, y)
:param heuristic: 启发式函数
:return: 最短路径列表
"""
# 创建起始节点和目标节点
start_node = Node(start)
end_node = Node(end)
# 初始化开放列表和关闭列表
open_list = []
closed_list = set()
# 将起始节点加入开放列表
heapq.heappush(open_list, start_node)
while open_list:
# 取出f值最小的节点
current_node = heapq.heappop(open_list)
closed_list.add(current_node.position)
# 如果到达目标节点,重构路径
if current_node.position == end_node.position:
path = []
current = current_node
while current:
path.append(current.position)
current = current.parent
return path[::-1]
# 生成邻居节点
neighbors = []
for direction in [(0, 1), (1, 0), (0, -1), (-1, 0), (1, 1), (-1, -1), (1, -1), (-1, 1)]:
neighbor_pos = (current_node.position[0] + direction[0],
current_node.position[1] + direction[1])
# 检查邻居是否在网格内且可通行
if (0 <= neighbor_pos[0] < len(grid) and
0 <= neighbor_pos[1] < len(grid[0]) and
grid[neighbor_pos[0]][neighbor_pos[1]] == 0):
neighbors.append(Node(neighbor_pos, current_node))
# 处理邻居节点
for neighbor in neighbors:
if neighbor.position in closed_list:
continue
# 计算g值
if abs(neighbor.position[0] - current_node.position[0]) + abs(neighbor.position[1] - current_node.position[1]) == 2:
# 对角线移动,成本为√2
neighbor.g = current_node.g + 1.414
else:
# 水平/垂直移动,成本为1
neighbor.g = current_node.g + 1
# 计算h值和f值
neighbor.h = heuristic(neighbor.position, end_node.position)
neighbor.f = neighbor.g + neighbor.h
# 检查是否在开放列表中且g值更小
in_open = False
for open_node in open_list:
if open_node.position == neighbor.position and open_node.g <= neighbor.g:
in_open = True
break
if not in_open:
heapq.heappush(open_list, neighbor)
return [] # 没有找到路径
启发式函数
def manhattan_distance(pos1, pos2):
"""曼哈顿距离(适用于四方向移动)"""
return abs(pos1[0] - pos2[0]) + abs(pos1[1] - pos2[1])
def euclidean_distance(pos1, pos2):
"""欧几里得距离(适用于八方向移动)"""
return ((pos1[0] - pos2[0]) ** 2 + (pos1[1] - pos2[1]) ** 2) ** 0.5
def chebyshev_distance(pos1, pos2):
"""切比雪夫距离(适用于棋盘移动)"""
return max(abs(pos1[0] - pos2[0]), abs(pos1[1] - pos2[1]))
def diagonal_distance(pos1, pos2):
"""对角线距离(适用于八方向移动)"""
dx = abs(pos1[0] - pos2[0])
dy = abs(pos1[1] - pos2[1])
return max(dx, dy) + (1.414 - 1) * min(dx, dy)
算法分析
启发式函数的性质
为了确保A*算法找到最优解,启发式函数 h(n)h(n)h(n) 应该满足以下条件:
- 可容性(Admissibility):h(n)≤h∗(n)h(n) \leq h^*(n)h(n)≤h∗(n),即启发式估计值不超过实际最优值
- 一致性(Consistency):h(n)≤w(u,v)+h(v)h(n) \leq w(u, v) + h(v)h(n)≤w(u,v)+h(v),对于所有节点 u,vu, vu,v
常见的启发式函数:
- 曼哈顿距离:适用于四方向移动的网格
- 欧几里得距离:适用于八方向移动的网格
- 切比雪夫距离:适用于棋盘移动
与其他算法的比较
| 特性 | A* | Dijkstra | BFS |
|---|---|---|---|
| 最优解保证 | 是(在满足启发式条件时) | 是 | 是 |
| 搜索效率 | 高(取决于启发式) | 中等 | 低 |
| 内存使用 | 较高 | 中等 | 较高 |
| 适用场景 | 有明确目标的路径规划 | 单源最短路径 | 无权图最短路径 |
| 实现复杂度 | 较复杂 | 简单 | 简单 |
优化策略
- 双向A*:从起点和终点同时进行搜索
- 跳点搜索(Jump Point Search):针对网格图的优化
- 内存限制A*:限制内存使用,避免内存溢出
- 时间限制A*:限制搜索时间,返回当前最优解
双向A*实现
def bidirectional_a_star(grid, start, end, heuristic):
"""
双向A*算法实现
"""
# 初始化两个方向的搜索
forward_open = []
backward_open = []
forward_closed = set()
backward_closed = set()
start_node = Node(start)
end_node = Node(end)
heapq.heappush(forward_open, start_node)
heapq.heappush(backward_open, end_node)
while forward_open and backward_open:
# 前向搜索
current_forward = heapq.heappop(forward_open)
forward_closed.add(current_forward.position)
# 检查是否与反向搜索相遇
if current_forward.position in backward_closed:
# 重构完整路径
path = []
# 从前向搜索重构路径
temp = current_forward
while temp:
path.append(temp.position)
temp = temp.parent
path.reverse()
# 从反向搜索重构路径
temp = find_node(backward_open, current_forward.position)
while temp:
path.append(temp.position)
temp = temp.parent
return path
# 扩展前向搜索
for neighbor_pos in get_neighbors(current_forward.position, grid):
if neighbor_pos in forward_closed:
continue
neighbor = Node(neighbor_pos, current_forward)
neighbor.g = current_forward.g + 1
neighbor.h = heuristic(neighbor_pos, end)
neighbor.f = neighbor.g + neighbor.h
heapq.heappush(forward_open, neighbor)
# 反向搜索(类似前向搜索)
current_backward = heapq.heappop(backward_open)
backward_closed.add(current_backward.position)
if current_backward.position in forward_closed:
# 重构路径(类似前向搜索)
path = []
temp = current_backward
while temp:
path.append(temp.position)
temp = temp.parent
path.reverse()
temp = find_node(forward_open, current_backward.position)
while temp:
path.append(temp.position)
temp = temp.parent
return path
# 扩展反向搜索
for neighbor_pos in get_neighbors(current_backward.position, grid):
if neighbor_pos in backward_closed:
continue
neighbor = Node(neighbor_pos, current_backward)
neighbor.g = current_backward.g + 1
neighbor.h = heuristic(neighbor_pos, start)
neighbor.f = neighbor.g + neighbor.h
heapq.heappush(backward_open, neighbor)
return [] # 没有找到路径
注意事项
- 确保启发式函数满足可容性条件,以保证找到最优解
- 对于大规模问题,注意内存使用,考虑使用内存限制版本
- 在实现时注意处理重复节点和路径重构
- 可以根据具体问题调整启发式函数以获得更好的性能
总结
A* 算法作为启发式搜索的经典算法,在路径规划领域具有重要价值。通过结合已知成本和预估成本,A* 算法能够在保证最优解的同时显著提高搜索效率。算法的关键在于启发式函数的设计和优化,合适的启发式函数可以大大减少搜索空间。A* 算法的灵活性和高效性使其成为游戏AI、导航系统、机器人路径规划等领域的首选算法。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)