1. 项目概述
路径规划是机器人导航、自动驾驶、游戏AI等领域的核心问题。在这个项目中,我们将使用Matlab实现三种经典的路径规划算法:A*、Dijkstra和D*。这三种算法各有特点,适用于不同场景,但都遵循"寻找最优路径"这一共同目标。
我最初接触路径规划是在研究生时期的机器人项目中,当时为了找到一个可靠的导航方案,几乎把所有主流算法都试了一遍。现在回想起来,如果能早点掌握这些算法的实现要点,可以少走很多弯路。这也是我写这篇教程的初衷——帮助新手快速入门,避免重复踩坑。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 算法原理与比较
2.1 Dijkstra算法:基础中的基础
Dijkstra算法由荷兰计算机科学家Edsger W. Dijkstra于1956年提出,是最经典的图搜索算法之一。它的核心思想是"贪心"策略——每次选择当前已知的最短路径节点进行扩展。
算法步骤如下:
- 初始化:设置起点距离为0,其他节点距离为无穷大
- 循环执行:
- 从待处理节点中选择距离最小的节点u
- 对u的每个邻居v,计算通过u到达v的距离
- 如果新距离更小,则更新v的距离
- 直到终点被处理或所有可达节点都被处理
Dijkstra的优点是保证找到最短路径,缺点是计算量大,因为它会均匀地探索所有方向。
注意:Dijkstra算法要求所有边的权重为非负数,这在路径规划中通常成立(距离、时间等都不会是负值)。
2.2 A*算法:启发式搜索的典范
A*算法是对Dijkstra的改进,通过引入启发式函数(heuristic)来指导搜索方向。启发式函数h(n)估计从节点n到终点的距离,常用的有:
- 曼哈顿距离:适用于网格地图,只能沿固定方向移动
- 欧几里得距离:直线距离,适用于自由移动场景
A*的评价函数f(n) = g(n) + h(n),其中g(n)是从起点到n的实际距离。
关键区别在于节点的选择标准:
- Dijkstra:按g(n)排序
- A*:按f(n)排序
这使得A*能够更有方向性地向终点搜索,效率通常更高。
2.3 D*算法:动态环境的应对者
D*(D-Star)算法是为动态环境设计的增量式搜索算法。当环境发生变化时(如出现新障碍物),它不需要完全重新规划,而是能高效地修正原有路径。
D*的核心特点:
- 反向搜索:从目标点向起点搜索
- 维护两个估计值:k和h
- 当环境变化时,只更新受影响的部分
这使得D*特别适合机器人导航等实时性要求高的场景。
3. Matlab实现详解
3.1 环境建模
首先需要建立地图模型。我们使用二维矩阵表示网格地图:
matlab复制% 创建20x20的地图,1表示障碍物,0表示自由空间
map = zeros(20,20);
map(5:15,10) = 1; % 添加一道垂直障碍墙
map(10,5:15) = 1; % 添加一道水平障碍墙
可视化地图:
matlab复制imagesc(map);
colormap([1 1 1; 0 0 0]); % 白色表示自由,黑色表示障碍
axis equal;
3.2 Dijkstra实现
matlab复制function [path, cost] = dijkstra(map, start, goal)
[rows, cols] = size(map);
distances = inf(rows, cols); % 初始化所有距离为无穷大
distances(start(1), start(2)) = 0;
% 优先队列存储待处理节点
queue = PriorityQueue();
queue.insert([start(1), start(2)], 0);
% 记录路径
came_from = zeros(rows, cols, 2);
while ~queue.isEmpty()
[current, ~] = queue.pop();
% 如果到达目标点
if isequal(current, goal)
path = reconstruct_path(came_from, goal);
cost = distances(goal(1), goal(2));
return;
end
% 检查四个邻居(上、下、左、右)
neighbors = [current(1)-1, current(2);
current(1)+1, current(2);
current(1), current(2)-1;
current(1), current(2)+1];
for i = 1:size(neighbors,1)
n = neighbors(i,:);
% 检查是否在地图范围内且不是障碍物
if n(1) >= 1 && n(1) <= rows && ...
n(2) >= 1 && n(2) <= cols && ...
map(n(1), n(2)) == 0
% 计算新距离
new_dist = distances(current(1), current(2)) + 1;
% 如果找到更短路径
if new_dist < distances(n(1), n(2))
distances(n(1), n(2)) = new_dist;
came_from(n(1), n(2), :) = current;
queue.insert(n, new_dist);
end
end
end
end
% 如果没有找到路径
path = [];
cost = inf;
end
3.3 A*算法实现
A*的主要区别在于优先队列的排序标准:
matlab复制function [path, cost] = astar(map, start, goal)
% ...(初始化部分与Dijkstra类似)
% 启发式函数(这里使用曼哈顿距离)
heuristic = @(pos) abs(pos(1)-goal(1)) + abs(pos(2)-goal(2));
while ~queue.isEmpty()
[current, ~] = queue.pop();
% ...(目标检查与Dijkstra相同)
for i = 1:size(neighbors,1)
n = neighbors(i,:);
% ...(边界检查与Dijkstra相同)
% 计算新距离
new_g = distances(current(1), current(2)) + 1;
% 如果找到更短路径
if new_g < distances(n(1), n(2))
distances(n(1), n(2)) = new_g;
came_from(n(1), n(2), :) = current;
priority = new_g + heuristic(n); % A*的关键区别
queue.insert(n, priority);
end
end
end
% ...(路径重构与Dijkstra相同)
end
3.4 D*算法实现
D*的实现较为复杂,这里给出简化版本的核心逻辑:
matlab复制function [path, cost] = dstar(map, start, goal)
% 初始化
[rows, cols] = size(map);
k_m = 0; % 移动距离
rhs = inf(rows, cols);
g = inf(rows, cols);
rhs(goal(1), goal(2)) = 0;
queue = PriorityQueue();
queue.insert([goal(1), goal(2)], calculate_key(goal, start));
% 主循环
while ~queue.isEmpty() && ...
(queue.min_key() < calculate_key(start, start) || ...
rhs(start(1), start(2)) ~= g(start(1), start(2)))
[u, k_old] = queue.pop();
if k_old < calculate_key(u, start)
queue.insert(u, calculate_key(u, start));
elseif g(u(1), u(2)) > rhs(u(1), u(2))
g(u(1), u(2)) = rhs(u(1), u(2));
% 更新邻居节点...
else
g(u(1), u(2)) = inf;
% 更新当前节点和邻居节点...
end
end
% ...(路径重构和其他细节)
end
function key = calculate_key(u, start)
% 计算节点的key值
global g rhs k_m;
key1 = min(g(u(1), u(2)), rhs(u(1), u(2))) + heuristic(u, start) + k_m;
key2 = min(g(u(1), u(2)), rhs(u(1), u(2)));
key = [key1, key2];
end
4. 性能比较与优化技巧
4.1 三种算法的对比实验
我们在20x20的网格地图上测试三种算法的性能:
| 算法 | 路径长度 | 探索节点数 | 运行时间(ms) |
|---|---|---|---|
| Dijkstra | 28 | 320 | 45 |
| A* | 28 | 120 | 18 |
| D* | 28 | 150 | 25 |
注意:这些数据是在特定测试环境下的结果,实际性能会因地图复杂度、起点终点位置等因素而变化。
4.2 优化技巧
-
优先队列的实现:
- Matlab内置的优先队列性能有限
- 可以改用二叉堆或Fibonacci堆实现
- 对于大型地图,这能带来显著性能提升
-
启发式函数的选择:
- 在允许对角移动的地图中,欧几里得距离更合适
- 对于网格地图,曼哈顿距离计算更快
- 可以尝试不同的启发式权重(加权A*)
-
地图表示优化:
- 使用稀疏矩阵存储大型稀疏地图
- 对规则障碍物使用更高级的表示方法(如四叉树)
-
预处理技术:
- 对于固定地图,可以预计算某些信息
- 路标(waypoint)预处理可以加速后续搜索
5. 常见问题与调试技巧
5.1 算法不收敛或找不到路径
可能原因:
- 起点或终点被障碍物包围
- 地图数据有问题(如障碍物标记错误)
- 算法实现中存在逻辑错误
调试步骤:
- 可视化地图和算法搜索过程
- 检查边界条件和邻居节点的处理
- 添加调试输出,跟踪关键变量的变化
5.2 找到的路径不是最优的
常见于A*算法,可能原因:
- 启发式函数不满足可采纳性(overestimating)
- 节点扩展顺序有问题
- 代价函数计算不正确
解决方案:
- 确保启发式函数不会高估实际距离
- 检查优先队列的实现是否正确
- 验证g(n)的计算是否准确
5.3 性能问题
对于大型地图,算法可能运行缓慢:
- 尝试更高效的优先队列实现
- 考虑分层路径规划(先粗后细)
- 使用JIT加速(Matlab的codegen)
6. 实际应用案例
6.1 机器人导航
在机器人导航中,我们通常需要:
- 使用D或D Lite处理动态障碍物
- 结合传感器数据实时更新地图
- 考虑机器人的运动约束(如最小转弯半径)
6.2 游戏AI
游戏中的NPC路径规划:
- A*是最常用的选择
- 可以预处理导航网格(NavMesh)
- 使用HPA*(分层A*)处理大型游戏世界
6.3 物流路径优化
在仓库机器人调度中:
- 需要同时规划多个机器人的路径
- 考虑避碰和优先级
- 可以结合时间维度扩展状态空间
7. 扩展与进阶方向
掌握了基础实现后,你可以进一步探索:
- 混合A*:结合A和D的优点
- Any-angle路径规划:突破网格限制
- 多目标路径规划:同时优化多个指标(距离、时间、能耗)
- 机器学习增强:使用学习到的启发式函数
我在实际项目中发现,理解这些算法的核心思想比记住具体实现更重要。当你遇到新的路径规划问题时,可以基于这些基本原理进行适应和扩展。比如,在最近的一个无人机项目中,我们就修改了A*的代价函数,加入了风场影响的因素。
