A* 是一种路径规划算法。
它解决的问题是:
我有一张地图 我知道起点 start 我知道终点 goal 地图里有障碍物 我要找一条从起点到终点的可走路线比如地图是:
grid = [ [0, 0, 0, 0, 0], [1, 1, 0, 1, 0], [0, 0, 0, 1, 0], [0, 1, 0, 0, 0] ]这里:
0 = 可以走 1 = 障碍物,不能走你可以把它想成一个棋盘:
第0行: 0 0 0 0 0 第1行: 1 1 0 1 0 第2行: 0 0 0 1 0 第3行: 0 1 0 0 0起点:
start = (0, 0)终点:
goal = (4, 3)注意坐标是:
(x, y) x = 第几列 y = 第几行所以(0, 0)是左上角。
(4, 3)是第 4 列、第 3 行,也就是右下附近。
2. A* 的核心思想
A* 不会乱走。
它每次都会判断:
哪个点最值得我先探索?判断方法靠这个公式:
f = g + h这里三个字母很重要。
g = 从起点走到当前点,已经走了多少步 h = 从当前点到终点,估计还要走多少步 f = 总估计代价比如当前有一个点:
current = (2, 1)如果从起点走到它已经用了 3 步:
g = 3它离终点估计还差 4 步:
h = 4那么:
f = g + h = 7另一个点:
g = 5 h = 6 f = 11那 A* 会优先看f = 7的点。
因为它看起来更有希望更快到终点。
3. h 怎么算?
这里的h叫 heuristic,中文一般叫“启发式函数”。
最简单的是曼哈顿距离:
abs(x1 - x2) + abs(y1 - y2)它的意思是:
横着差多少 + 竖着差多少比如:
current = (2, 1) goal = (4, 3)那:
x 差距 = |2 - 4| = 2 y 差距 = |1 - 3| = 2 h = 2 + 2 = 4对应代码:
def heuristic(a, b): x1, y1 = a x2, y2 = b return abs(x1 - x2) + abs(y1 - y2)逐句解释:
def heuristic(a, b):定义一个函数,输入两个点。
比如:
a = (2, 1) b = (4, 3)然后:
x1, y1 = a x2, y2 = b把坐标拆开。
return abs(x1 - x2) + abs(y1 - y2)返回横向距离加纵向距离。
4. 找邻居 get_neighbors
A* 每次处理一个点时,要看它周围哪些点能走。
比如当前点:
node = (2, 1)它周围理论上有四个方向:
右边: (3, 1) 左边: (1, 1) 下边: (2, 2) 上边: (2, 0)代码是:
def get_neighbors(node, grid): x, y = node directions = [ (1, 0), (-1, 0), (0, 1), (0, -1) ] neighbors = [] for dx, dy in directions: nx = x + dx ny = y + dy if 0 <= ny < len(grid) and 0 <= nx < len(grid[0]): if grid[ny][nx] == 0: neighbors.append((nx, ny)) return neighbors这段很关键。
先看:
x, y = node如果:
node = (2, 1)那么:
x = 2 y = 1再看:
directions = [ (1, 0), (-1, 0), (0, 1), (0, -1) ]这是四个移动方向。
(1, 0) = x + 1,往右 (-1, 0) = x - 1,往左 (0, 1) = y + 1,往下 (0, -1) = y - 1,往上然后:
neighbors = []创建一个空 list,用来装“可以走的邻居”。
接着:
for dx, dy in directions:意思是一个方向一个方向试。
第一次:
dx = 1 dy = 0第二次:
dx = -1 dy = 0第三次:
dx = 0 dy = 1第四次:
dx = 0 dy = -1然后:
nx = x + dx ny = y + dy算出新点坐标。
比如当前点(2, 1),方向(1, 0):
nx = 2 + 1 = 3 ny = 1 + 0 = 1所以新点是:
(3, 1)然后检查有没有出界:
if 0 <= ny < len(grid) and 0 <= nx < len(grid[0]):这句话意思是:
ny 必须在合法行范围内 nx 必须在合法列范围内比如这个地图有 4 行:
len(grid) == 4所以 y 只能是:
0, 1, 2, 3每行有 5 列:
len(grid[0]) == 5所以 x 只能是:
0, 1, 2, 3, 4如果新点没出界,再看是不是障碍物:
if grid[ny][nx] == 0:这里一定注意:
grid[ny][nx]不是:
grid[nx][ny]因为二维 list 是:
grid[第几行][第几列]而:
y = 行 x = 列所以是:
grid[y][x]如果这个格子是0,说明可以走:
neighbors.append((nx, ny))把这个点加入邻居列表。
最后:
return neighbors把所有可走邻居返回。
5. A* 主函数
完整主函数是:
def astar(grid, start, goal): open_list = [] heapq.heappush(open_list, (0, start)) came_from = {} g_score = {} g_score[start] = 0 closed_set = set() while open_list: current_f, current = heapq.heappop(open_list) if current == goal: return reconstruct_path(came_from, current) closed_set.add(current) for neighbor in get_neighbors(current, grid): if neighbor in closed_set: continue tentative_g = g_score[current] + 1 if neighbor not in g_score or tentative_g < g_score[neighbor]: came_from[neighbor] = current g_score[neighbor] = tentative_g h = heuristic(neighbor, goal) f = tentative_g + h heapq.heappush(open_list, (f, neighbor)) return None我们拆开讲。
6. open_list 是什么
open_list = []open_list是:
等待被探索的点也就是候选点。
然后:
heapq.heappush(open_list, (0, start))意思是把起点放进去。
注意放进去的是:
(0, start)不是单纯的:
start因为 A* 要按照f值排序。
比如:
(7, (2, 1))意思是:
点 (2,1) 的 f 值是 7heapq的作用就是:
每次自动帮你拿出 f 最小的点7. came_from 是什么
came_from = {}这是一个字典。
作用是:
记录每个点是从哪里来的比如:
came_from[(2, 1)] = (2, 0)意思是:
(2,1) 这个点是从 (2,0) 走来的最后找到终点之后,就靠这个字典倒推路径。
8. g_score 是什么
g_score = {} g_score[start] = 0g_score也是字典。
它记录:
从起点到某个点已经用了多少步起点到起点当然是 0:
g_score[start] = 0比如之后可能有:
g_score[(2, 1)] = 3意思是:
从起点走到 (2,1) 已经用了 3 步9. closed_set 是什么
closed_set = set()这是集合。
作用是:
记录已经处理过的点比如一个点已经被处理完了,就放进去:
closed_set.add(current)之后如果又遇到它:
if neighbor in closed_set: continue就跳过。
这样可以避免重复绕圈。
10. while open_list 是什么
while open_list:意思是:
只要 open_list 里还有候选点,就继续找如果 open_list 空了,还没找到终点,说明没有路。
所以最后:
return None意思是找不到路径。
11. 取出 f 最小的点
current_f, current = heapq.heappop(open_list)这句意思是:
从 open_list 里面取出 f 最小的点比如取出来:
current_f = 5 current = (2, 1)那当前要处理的点就是(2,1)。
这就是 A* 的核心:
每次优先处理 f 最小的点12. 判断是不是终点
if current == goal: return reconstruct_path(came_from, current)意思是:
如果当前点就是终点,说明路径找到了然后调用:
reconstruct_path(came_from, current)把完整路径还原出来。
13. 把 current 标记为处理过
closed_set.add(current)意思是:
这个点我已经检查过它的邻居了 以后别重复处理14. 检查 current 的邻居
for neighbor in get_neighbors(current, grid):这句意思是:
把当前点上下左右能走的点拿出来,一个一个检查比如:
current = (2, 1)可能返回:
[(2, 2), (2, 0)]然后循环检查每个邻居。
15. 如果邻居处理过,就跳过
if neighbor in closed_set: continuecontinue的意思是:
跳过这一次循环,直接看下一个 neighbor也就是说:
如果这个邻居已经处理过了,就别重复处理16. 计算 tentative_g
tentative_g = g_score[current] + 1这句非常重要。
tentative_g的意思是:
如果我从 current 走到 neighbor,那么 neighbor 的新 g 值是多少?因为每走一步代价是 1。
所以:
neighbor 的 g = current 的 g + 1比如:
g_score[current] = 3那么:
tentative_g = 4意思是如果从当前点走到邻居,邻居离起点就是 4 步。
17. 判断这条路线是不是更好
if neighbor not in g_score or tentative_g < g_score[neighbor]:这句意思是:
如果这个邻居以前没见过 或者 这次走到它的路线比以前更短 那就更新比如以前:
g_score[neighbor] = 8现在:
tentative_g = 5说明现在这条路更短。
所以更新。
18. 更新 came_from
came_from[neighbor] = current意思是:
neighbor 是从 current 走过来的比如:
came_from[(2, 2)] = (2, 1)意思是:
(2,2) 的上一站是 (2,1)19. 更新 g_score
g_score[neighbor] = tentative_g意思是:
记录从起点走到 neighbor 需要多少步20. 计算 h 和 f
h = heuristic(neighbor, goal) f = tentative_g + h这里:
tentative_g = g h = 估计到终点还差多少 f = g + h比如:
g = 4 h = 3 f = 721. 把 neighbor 加入 open_list
heapq.heappush(open_list, (f, neighbor))意思是:
把这个邻居加入候选点列表 它的优先级是 f之后 A* 会继续从open_list里拿出f最小的点。
22. reconstruct_path 是什么
代码:
def reconstruct_path(came_from, current): path = [current] while current in came_from: current = came_from[current] path.append(current) path.reverse() return path这个函数负责:
从终点倒着找回起点比如最终到了:
current = (4, 3)然后came_from里记录:
came_from[(4, 3)] = (3, 3) came_from[(3, 3)] = (2, 3) came_from[(2, 3)] = (2, 2) came_from[(2, 2)] = (2, 1) came_from[(2, 1)] = (2, 0) came_from[(2, 0)] = (1, 0) came_from[(1, 0)] = (0, 0)那它就会倒着找:
(4,3) (3,3) (2,3) (2,2) (2,1) (2,0) (1,0) (0,0)但这个顺序是:
终点 → 起点所以需要:
path.reverse()变成:
起点 → 终点最后返回:
[(0, 0), (1, 0), (2, 0), (2, 1), (2, 2), (2, 3), (3, 3), (4, 3)]23. 完整代码
你可以把 Day 03 的完整代码写成这样:
import heapq def heuristic(a, b): x1, y1 = a x2, y2 = b return abs(x1 - x2) + abs(y1 - y2) def get_neighbors(node, grid): x, y = node directions = [ (1, 0), (-1, 0), (0, 1), (0, -1) ] neighbors = [] for dx, dy in directions: nx = x + dx ny = y + dy if 0 <= ny < len(grid) and 0 <= nx < len(grid[0]): if grid[ny][nx] == 0: neighbors.append((nx, ny)) return neighbors def reconstruct_path(came_from, current): path = [current] while current in came_from: current = came_from[current] path.append(current) path.reverse() return path def astar(grid, start, goal): open_list = [] heapq.heappush(open_list, (0, start)) came_from = {} g_score = {} g_score[start] = 0 closed_set = set() while open_list: current_f, current = heapq.heappop(open_list) if current == goal: return reconstruct_path(came_from, current) closed_set.add(current) for neighbor in get_neighbors(current, grid): if neighbor in closed_set: continue tentative_g = g_score[current] + 1 if neighbor not in g_score or tentative_g < g_score[neighbor]: came_from[neighbor] = current g_score[neighbor] = tentative_g h = heuristic(neighbor, goal) f = tentative_g + h heapq.heappush(open_list, (f, neighbor)) return None grid = [ [0, 0, 0, 0, 0], [1, 1, 0, 1, 0], [0, 0, 0, 1, 0], [0, 1, 0, 0, 0] ] start = (0, 0) goal = (4, 3) path = astar(grid, start, goal) print(path)24. 最后用人话串起来
A* 干的事情就是:
先把起点放进 open_list 然后每次拿出 f 最小的点 看它是不是终点 如果不是,就找它周围能走的邻居 给邻居计算 g、h、f 如果发现更短路线,就更新 came_from 和 g_score 然后把邻居放回 open_list 一直重复 直到找到终点最重要的 5 个变量你先背下来:
open_list = 还没探索、但可以考虑的点 closed_set = 已经探索过的点 g_score = 从起点走到某点已经用了多少步 heuristic = 估计某点到终点还差多少步 came_from = 记录路径,每个点的上一站是谁