迷宫求解最短路径算法实现
迷宫求解最短路径算法实现
你有没有遇到过这种情况:看着一个小机器人在房间里兜兜转转,怎么都找不到出口?或者玩一款RPG游戏时,NPC绕了大半个地图才走到你面前……😅 其实背后很可能就是“迷宫寻路”这个经典问题没处理好!
在现实世界中,从扫地机器人规划清扫路线,到自动驾驶汽车避开障碍,再到物流仓库里的AGV小车自动调度——它们本质上都在解决同一个问题: 如何从A点最快、最短地到达B点?
而今天我们要聊的,正是这个问题的核心武器: BFS(广度优先搜索)和 A* 算法 。别被名字吓到,咱们不堆公式,也不搞玄学推导,直接上干货,带你从零实现一个能“看懂”迷宫的智能路径引擎 🚀
想象一下,我们手里有一张二维网格图,每个格子要么是墙(1),要么是路(0)。任务是从起点
(sx, sy)
走到终点
(ex, ey)
,每一步只能上下左右移动,并且要走最少步数。
这看起来像不像小时候玩的纸上迷宫?但计算机可不会“凭感觉”走捷径,它需要一套明确的规则来探索所有可能的路径,同时保证找到的是 真正最短的那一条 。
这时候,两种主流策略就登场了:
- BFS :像个耐心的探路者,一层层往外扩散,绝不漏掉任何角落;
- A* :更聪明些,边走边估算“离目标还有多远”,优先往“看起来更近”的方向走。
听起来是不是有点像直觉 vs 理性?下面我们一个个拆开看看。
先说 BFS —— 它可能是最直观也最可靠的无权图最短路径解法。
核心思想特别简单: 按距离分层遍历 。从起点出发,先把所有一步能到的位置标记为“第1层”,再把从这些位置能到达的新位置标记为“第2层”,依此类推。一旦碰到终点,立刻停下——因为这是第一次抵达,所以肯定是最短路径 ✅
为了做到这一点,我们需要:
- 一个队列(
deque
)来管理待访问节点;
- 一张“已访问”表,防止重复进入;
- 一个父节点记录表,方便最后反向追踪路径。
来看一段干净利落的 Python 实现:
from collections import deque
def bfs_shortest_path(maze, start, end):
rows, cols = len(maze), len(maze[0])
visited = [[False] * cols for _ in range(rows)]
parent = [[None] * cols for _ in range(rows)]
queue = deque([start])
directions = [(0, 1), (1, 0), (0, -1), (-1, 0)] # 右、下、左、上
visited[start[0]][start[1]] = True
while queue:
x, y = queue.popleft()
if (x, y) == end:
break
for dx, dy in directions:
nx, ny = x + dx, y + dy
if 0 <= nx < rows and 0 <= ny < cols and not visited[nx][ny] and maze[nx][ny] == 0:
visited[nx][ny] = True
parent[nx][ny] = (x, y)
queue.append((nx, ny))
# 回溯路径
path = []
curr = end
while curr is not None:
path.append(curr)
curr = parent[curr[0]][curr[1]]
path.reverse()
return path if path[0] == start else []
这段代码虽然不长,但藏着几个关键细节 💡:
- 使用
visited
数组避免无限循环;
-
parent
存的是坐标对,相当于给每个格子记下“我是谁带进来的”;
- 最后通过不断回溯
parent
拼出完整路径;
- 如果起点根本不可达,返回空列表,调用方可以据此判断失败。
时间复杂度是 O(V + E),对于 N×N 的迷宫来说大约是 O(N²),空间也是 O(N²) —— 对中小型场景完全够用。
那如果迷宫变得超级大呢?比如一个 1000×1000 的厂区地图?这时候 BFS 可能会“地毯式搜索”太多无效区域,效率就下来了。
怎么办?让 A* 上场吧!✨
A* 的厉害之处在于: 它不仅知道已经走了多远,还能“猜”还剩多远到终点 。
这就是所谓的“启发式函数”。比如我们常用的曼哈顿距离:
def manhattan_distance(a, b):
return abs(a[0] - b[0]) + abs(a[1] - b[1])
它表示在只能上下左右移动的情况下,从 a 到 b 至少需要多少步。这个值永远不会高估真实代价(满足“可接纳性”),因此能保证最终路径一定是最优的 ✔️
A* 维护三个重要变量:
-
g(n)
:从起点到当前节点的实际步数;
-
h(n)
:从当前节点到终点的估计步数(启发);
-
f(n) = g(n) + h(n)
:综合评分,决定谁先被扩展。
它用一个最小堆(优先队列)来动态选择 f 值最小的节点进行探索,从而大幅减少盲目搜索的范围。
下面是完整的 A* 实现:
import heapq
def a_star(maze, start, end):
rows, cols = len(maze), len(maze[0])
directions = [(0, 1), (1, 0), (0, -1), (-1, 0)]
# 优先队列:(f_score, g_score, x, y)
open_set = [(manhattan_distance(start, end), 0, start[0], start[1])]
came_from = {} # 父节点记录
g_score = {start: 0}
visited = set()
while open_set:
f, g, x, y = heapq.heappop(open_set)
if (x, y) == end:
break
if (x, y) in visited:
continue
visited.add((x, y))
for dx, dy in directions:
nx, ny = x + dx, y + dy
if 0 <= nx < rows and 0 <= ny < cols and maze[nx][ny] == 0:
tentative_g = g + 1
neighbor = (nx, ny)
if neighbor not in g_score or tentative_g < g_score[neighbor]:
g_score[neighbor] = tentative_g
f_score = tentative_g + manhattan_distance(neighbor, end)
heapq.heappush(open_set, (f_score, tentative_g, nx, ny))
came_from[neighbor] = (x, y)
# 构建路径
path = []
curr = end
while curr in came_from:
path.append(curr)
curr = came_from[curr]
path.append(start)
path.reverse()
return path if path[0] == start else []
你会发现,相比 BFS,A* 多了一个
g_score
字典和优先队列机制。但它能在复杂地图中节省大量计算资源,尤其当终点方向明确时,几乎像“长了眼睛”一样直奔目标而去 👀
举个例子:在一个有走廊结构的大迷宫里,BFS 会像水波一样均匀扩散,而 A* 会迅速锁定通向终点的主要通道,跳过很多死胡同。
当然啦,天下没有免费午餐 🍔
A* 的性能高度依赖于启发函数的质量。如果你用了欧几里得距离但不允许斜向移动,可能会轻微高估代价,影响最优性;反之,如果启发太保守(比如恒为0),那就退化成 Dijkstra 了。
所以记住一句话: 越贴近实际移动方式的启发函数,效果越好 。
那么这套技术到底用在哪呢?
其实早就无处不在了 🔍
比如你家的扫地机器人,它不是随机乱撞,而是先把房间建模成网格地图,然后用类似 A* 的算法规划覆盖路径;游戏中的 NPC 寻路基本都基于 A* 或其优化版本(如 JPS、Flow Field);就连无人配送车在园区穿行,底层也离不开这些搜索逻辑。
典型的系统架构大概是这样:
+---------------------+
| 用户界面 / 输入 |
+----------+----------+
|
v
+---------------------+
| 迷宫建模与预处理 |
| (网格化、障碍识别)|
+----------+----------+
|
v
+---------------------+
| 核心路径搜索引擎 |
| (BFS 或 A*) |
+----------+----------+
|
v
+---------------------+
| 输出:路径坐标序列 |
| 或可视化图形显示 |
+---------------------+
工作流程也很清晰:
1. 输入可以是图片、文本矩阵或 JSON 地图;
2. 预处理转换为 0/1 网格;
3. 设置起点终点;
4. 调用算法求解;
5. 返回路径并可视化(比如用 Matplotlib 画出来);
实际开发中还有一些工程技巧值得提一嘴:
- 对于超大地图,可以用位图压缩存储
visited
状态,省内存;
- 若支持八方向移动,建议使用切比雪夫距离或对角线启发;
- 在嵌入式设备上跑不动复杂堆操作?那就用简化版 BFS 更稳;
- 原始路径可能拐弯太多?加个后期平滑处理(比如路径剪枝或贝塞尔拟合),让运动更流畅;
- 动态障碍怎么办?可以把 A* 改造成增量式(如 LPA*)或结合 Replan 机制。
说到这里,你可能会问:现在都有深度学习了,为啥还要手写搜索算法?
答案是: 确定性 + 可解释性 + 实时性 。
AI模型虽然能“学会”走路,但在关键系统中,我们往往更希望知道“为什么走这条路”。而 BFS 和 A* 不仅理论完备、结果可靠,还能轻松集成到 ROS、Unity、Unreal 等主流平台中。
未来的发展方向也在融合:
- 把神经网络当成“更好的启发函数”,预测哪些区域更值得探索;
- 扩展到三维空间或多楼层建筑导航;
- 结合 SLAM 实现未知环境下的在线建图与路径生成;
- 多智能体协同路径规划,避免碰撞与拥堵。
总而言之,掌握 BFS 和 A* 并不只是为了刷题拿 offer 😄
它是通往智能移动世界的钥匙之一。无论是做一个小游戏 AI,还是设计一台真正的自主机器人,理解这些基础算法都能让你写出更高效、更鲁棒的代码。
下次当你看到一个小车安静地穿过迷宫直达终点时,不妨想想:它的“大脑”里,也许正运行着一段简洁而优雅的搜索逻辑 🧠💫
更多推荐


所有评论(0)