简介:这份资源提供基于蚁群算法的二维路径规划完整代码,面向机器人导航、物流配送与地图寻路方向的学习者及算法入门者,帮助理解如何用启发式优化方法在含障碍物的网格地图中搜索最优路径。压缩包共5个文件,以3个txt数据文件和2个m脚本为主,txt用于存放地图、障碍物与连线等基础数据,m文件承担算法主流程与路径规划逻辑,整体约4KB,结构轻量便于快速阅读与二次修改。目前已有755人学习下载。代码围绕蚁群算法的初始化、路径选择、信息素更新与蒸发、最优路径强化及迭代终止等环节展开,并涉及信息素浓度、蒸发率、启发式因子、蚂蚁数量等关键参数的调节,读者可据此观察参数变化对收敛速度与路径质量的影响,也可结合MATLAB完成地图构建、障碍物表示与最终路径可视化,适合作为课程设计、算法实验或自主导航原型开发的参考素材。
1. 蚁群算法做二维路径规划:从栅格地图到可运行代码的完整落地
很多人第一次接触蚁群算法,是被它“模拟蚂蚁找食物”的设定吸引,但真正卡住的地方往往不是算法本身,而是二维路径规划里那些琐碎却致命的工程细节:栅格地图怎么建、信息素怎么初始化、蚂蚁怎么走才不穿墙、参数一改结果就崩。我见过不少号称“蚁群算法matlab代码”或“蚁群算法python代码”的示例,跑出来路径贴着障碍物边缘抖,或者干脆绕远路,问题基本都出在落地环节而不是公式。这篇笔记就围绕“基于蚁群算法的二维路径规划代码”这个标题,把从环境建模到参数调优、从可复现代码到避坑排查的整条链路讲清楚。适合已经会写基础循环、想把这套算法真正用起来的同学,也适合做过一版但效果不稳定的熟手对照排查。
2. 二维栅格地图与蚁群算法的核心机制:先搞懂再动手
2.1 为什么二维路径规划偏爱栅格地图
二维路径规划的输入通常是一张环境图,常见做法是把它离散成栅格:每个格子标记为可通行或障碍。栅格地图的好处是坐标和索引能直接换算,蚂蚁在格子间移动时,邻居关系固定,判断是否撞墙只需要查一个二维数组。相比连续坐标下的几何碰撞检测,栅格化把问题简化成图搜索,蚁群算法的状态转移才好写。
栅格地图一般用 0/1 矩阵表示,0 表示可走,1 表示障碍。起点和终点也是格子坐标。地图分辨率决定了路径精度和计算量:格子越小路径越细腻,但蚂蚁可选的邻居变多,迭代时间上升。我一般先用 20×20 到 50×50 的栅格做验证,确认算法逻辑没问题再放大。
需要留意的是,栅格地图里蚂蚁的移动方向通常取 8 邻域(上下左右加四个对角),这样路径不会只有直角拐弯。对角移动的代价要按欧氏距离算,否则会出现“斜着走和直着走一样长”的失真。
2.2 蚁群算法的状态转移与信息素更新
蚁群算法的核心是两只手:一只负责“怎么选下一步”,一只负责“怎么留下痕迹”。状态转移概率决定蚂蚁从当前格子选哪个邻居,公式里揉进了信息素浓度和启发式信息。启发式信息一般取当前格子到终点的距离倒数,让蚂蚁有向终点靠拢的倾向。信息素则记录历史经验,好的路径上信息素越积越多,后续蚂蚁更愿意走。
信息素更新分两步:先挥发,再累加。挥发系数 ρ 控制旧信息消失的速度,太小会让算法陷在早期差路径里,太大又让搜索变得随机。累加时通常只让每轮里路径较优的蚂蚁贡献信息素,贡献量与路径长度成反比。这样短路径获得更多信息素,形成正反馈。
一个容易被忽略的点是信息素上下限。如果不加限制,某些格子信息素会无限增长,导致所有蚂蚁走同一条路,搜索停滞。常见做法是设一个最大值和最小值,把信息素钳制在区间内,这也是后面调参时要盯住的参数。
2.3 用 Python 搭出可运行的最小版本
下面这段代码实现了一个最小可运行的蚁群算法二维路径规划,包含栅格地图、状态转移、信息素更新和主循环。地图用 0/1 矩阵,起点左上角,终点右下角,中间放一堵带缺口的墙。
import numpy as np # 20x20 栅格地图,0 可走,1 障碍 GRID = np.zeros((20, 20), dtype=int) GRID[5:15, 10] = 1 # 竖墙 GRID[5:15, 10] = 0 # 留缺口 GRID[5, 10] = 1 GRID[14, 10] = 1 START = (0, 0) END = (19, 19) N_ANTS = 30 # 蚂蚁数量 N_ITER = 80 # 迭代轮数 ALPHA = 1.0 # 信息素重要程度 BETA = 5.0 # 启发式重要程度 RHO = 0.3 # 挥发系数 Q = 100.0 # 信息素强度 PHER_MAX = 10.0 PHER_MIN = 0.01 # 8 邻域偏移 MOVES = [(-1,-1),(-1,0),(-1,1),(0,-1),(0,1),(1,-1),(1,0),(1,1)] def in_bounds(x, y): return 0 <= x < 20 and 0 <= y < 20 def heuristic(x, y): # 到终点的欧氏距离倒数,加小量防止除零 return 1.0 / (np.hypot(x - END[0], y - END[1]) + 1e-6) def build_path(pheromone): path = [START] visited = {START} cur = START while cur != END and len(path) < 400: neighbors = [] probs = [] for dx, dy in MOVES: nx, ny = cur[0] + dx, cur[1] + dy if not in_bounds(nx, ny): continue if GRID[nx, ny] == 1: continue if (nx, ny) in visited: continue tau = pheromone[nx, ny] ** ALPHA eta = heuristic(nx, ny) ** BETA neighbors.append((nx, ny)) probs.append(tau * eta) if not neighbors: break probs = np.array(probs, dtype=float) probs = probs / probs.sum() idx = np.random.choice(len(neighbors), p=probs) cur = neighbors[idx] path.append(cur) visited.add(cur) return path def path_length(path): total = 0.0 for i in range(1, len(path)): dx = path[i][0] - path[i-1][0] dy = path[i][1] - path[i-1][1] total += np.hypot(dx, dy) return total pheromone = np.ones((20, 20)) * 1.0 best_path = None best_len = float('inf') for it in range(N_ITER): paths = [] lengths = [] for _ in range(N_ANTS): p = build_path(pheromone) if p[-1] == END: paths.append(p) lengths.append(path_length(p)) if not paths: continue # 挥发 pheromone *= (1 - RHO) # 只让本轮最优贡献信息素 idx_best = int(np.argmin(lengths)) for (x, y) in paths[idx_best]: pheromone[x, y] += Q / lengths[idx_best] # 钳制上下限 pheromone = np.clip(pheromone, PHER_MIN, PHER_MAX) if lengths[idx_best] < best_len: best_len = lengths[idx_best] best_path = paths[idx_best] print("最优路径长度:", best_len) print("路径节点数:", len(best_path) if best_path else 0)这段代码的逻辑说明:build_path负责让一只蚂蚁从起点走到终点,每一步在可通行且未访问的邻居里按概率选择;概率由信息素的 ALPHA 次方和启发式的 BETA 次方相乘得到。主循环里每轮让所有蚂蚁各走一次,只保留到达终点的路径,然后挥发信息素,把本轮最短路径上的格子信息素增加 Q/长度。最后用上下限钳制,避免信息素极端化。
参数说明:N_ANTS太小搜索不充分,太大会拖慢每轮;ALPHA越大越依赖历史信息,容易早熟;BETA越大越贪心,可能错过绕行更优解;RHO控制遗忘速度,0.2 到 0.5 是常见区间;Q影响信息素累加强度,配合上下限一起调。第一次跑建议先用这组参数确认能出路径,再逐项改。
3. 参数怎么设、代码怎么改:从能跑到跑得好
3.1 信息素挥发系数与启发式权重的联动调法
很多人调参是单独改一个值看结果,但蚁群算法里 ρ 和 β 是联动的。ρ 大意味着旧信息素快速消失,搜索更依赖启发式,此时如果 β 也大,蚂蚁会变得非常贪心,路径容易贴着障碍物走,甚至在某些地图上找不到可行解。反过来 ρ 小、β 小,搜索随机性太强,收敛慢。
我一般按这个顺序调:先把 β 固定在 3 到 5 之间,让蚂蚁有基本的方向感;然后调 ρ,从 0.3 开始,如果发现多轮之后最优路径不再变化且明显不是最短,说明早熟,把 ρ 调大到 0.4 或 0.5;如果路径抖动大、每轮差异明显,把 ρ 调小到 0.2。最后再微调 β,观察路径是否贴边。贴边严重就降低 β,让信息素主导。
还有一个隐藏参数是信息素上限。如果上限设得太大,早期偶然出现的一条短路径会迅速垄断信息素,后续蚂蚁全走它。把上限压到 5 到 10 之间,能明显缓解早熟。下限也不能是 0,否则某些格子信息素归零后再也不会被选中,搜索空间被永久砍掉。
3.2 把路径平滑和避障约束加进代码
原始蚁群算法输出的路径是格子序列,直接画出来会有锯齿,而且对角移动可能穿过障碍物的角。实际使用前通常要做两件事:路径平滑和碰撞校验。
路径平滑的常见做法是拉直:从起点开始,尝试跳过中间节点直接连到更远的节点,如果这条直线不穿过任何障碍格,就删掉中间节点。这样能把锯齿路径压成少量转折点。碰撞校验则针对对角移动,检查两个相邻格子之间的连线是否经过障碍格。如果经过,就禁止这次对角移动。
下面是在原代码基础上增加拉直处理的片段:
def line_clear(p1, p2): # 检查 p1 到 p2 的直线是否穿过障碍格 x1, y1 = p1 x2, y2 = p2 steps = int(max(abs(x2 - x1), abs(y2 - y1)) * 2) + 1 for i in range(steps + 1): t = i / steps x = int(round(x1 + (x2 - x1) * t)) y = int(round(y1 + (y2 - y1) * t)) if GRID[x, y] == 1: return False return True def smooth_path(path): if not path: return path result = [path[0]] i = 0 while i < len(path) - 1: j = len(path) - 1 while j > i + 1: if line_clear(path[i], path[j]): break j -= 1 result.append(path[j]) i = j return result逻辑说明:line_clear用等距采样判断两点连线是否碰到障碍格,采样密度取坐标差的两倍,避免漏检。smooth_path从当前点尽量往后找最远的可直达点,找到就跳过去。参数方面,采样密度可以再加密,但会增加计算量;如果地图障碍很细,建议把采样步长设小一点。
避障约束还要注意起点和终点本身不能落在障碍格上,否则蚂蚁第一步就无路可走。建图时先检查这两个坐标。
3.3 用 matplotlib 把迭代过程和最终路径画出来
调参时最怕只看最终路径长度,看不到过程。把每轮最优长度和最终路径画出来,能快速判断是早熟还是没收敛。
import matplotlib.pyplot as plt # 假设 history 里存了每轮最优长度 plt.figure(figsize=(10, 4)) plt.subplot(1, 2, 1) plt.plot(history) plt.xlabel("iteration") plt.ylabel("best length") plt.title("convergence") plt.subplot(1, 2, 2) plt.imshow(GRID.T, cmap="gray_r", origin="lower") if best_path: xs = [p[0] for p in best_path] ys = [p[1] for p in best_path] plt.plot(xs, ys, "r-", linewidth=2) plt.title("path") plt.show()逻辑说明:左图看收敛曲线,如果很早变平且值偏大,就是早熟;如果一直震荡,说明随机性太强。右图看路径是否贴边、是否绕远。参数上,GRID.T是因为 imshow 默认把第一个维度当行,转置后和坐标对应更直观。
提示:画图时把障碍格和可通行格用不同颜色区分,路径叠加在上面,一眼就能看出穿墙或贴边问题。
4. 避坑与排查:蚁群路径规划最常见的 5 个翻车点
4.1 蚂蚁原地打转或走不出起点
现象:每轮结束后没有任何蚂蚁到达终点,路径长度始终是初始值。
原因:起点周围全是障碍,或者 8 邻域判断里把可通行格误判成障碍。也有可能是 visited 集合把起点邻居提前排除,导致第一步无路可走。
解决:先打印起点周围 8 个格子的 GRID 值,确认至少有一个 0。检查in_bounds和GRID[nx, ny] == 1的判断顺序,避免索引越界被当成障碍。visited 只记录已走过的格子,起点本身要放进去,但邻居判断时不要因为起点在 visited 里就跳过。
4.2 路径穿墙或贴着障碍物角走
现象:画出来的路径看起来穿过了障碍格,或者对角移动时擦着障碍角过去。
原因:对角移动没有做碰撞校验,两个对角格子之间的连线可能经过障碍格。另外栅格坐标和绘图坐标如果没对齐,视觉上也会误判。
解决:加入line_clear校验,禁止穿过障碍的对角移动。绘图时用origin="lower"并确认坐标轴方向和 GRID 索引一致。如果只是视觉错觉,打印路径上每个格子的 GRID 值确认。
4.3 收敛曲线很早就变平但路径不是最优
现象:迭代十几轮后最优长度不再下降,但明显还有更短的绕行路线。
原因:信息素上限太大或挥发系数太小,早期一条普通路径垄断了信息素,所有蚂蚁都走同一条路,搜索停滞。
解决:降低信息素上限,把 PHER_MAX 从 10 降到 5 甚至 3;增大 RHO 到 0.4 以上;增加蚂蚁数量,让每轮有更多探索。还可以只让全局最优和本轮最优共同贡献信息素,而不是只让本轮最优贡献。
4.4 每轮结果抖动大,最优路径反复变化
现象:收敛曲线上下震荡,每轮最优路径都不一样,最终结果不稳定。
原因:挥发系数太大,信息素留不住;或者启发式权重太小,蚂蚁选择过于随机。
解决:把 RHO 降到 0.2 左右,增大 BETA 到 5 以上,让蚂蚁有明确的方向倾向。同时检查随机种子,固定种子后对比不同参数,排除随机性干扰。
4.5 地图变大后运行慢到无法接受
现象:50×50 以上栅格时,每轮迭代要等很久,调参效率极低。
原因:每只蚂蚁每步都要遍历邻居并计算概率,蚂蚁数量和迭代轮数一上去,计算量成倍增长。
解决:先用小地图调好参数,再放大验证。把蚂蚁数量控制在 20 到 50 之间,迭代轮数 50 到 100 足够观察趋势。计算概率时用 numpy 向量化替代 Python 循环。如果只是验证算法,不必追求大地图。
5. 进阶技巧:用信息素热力图和多次运行验证算法稳定性
调参调到后面,光看路径已经不够了,我习惯把信息素矩阵画成热力图。热力图能直接暴露搜索的偏好:如果信息素集中在一条窄带上,说明算法已经收敛;如果到处都有残留,说明还在探索。把热力图和最终路径叠在一起看,能判断信息素是否真的引导蚂蚁走向了最优区域。
plt.figure(figsize=(6, 5)) plt.imshow(pheromone.T, cmap="hot", origin="lower") if best_path: xs = [p[0] for p in best_path] ys = [p[1] for p in best_path] plt.plot(xs, ys, "c-", linewidth=2) plt.colorbar() plt.title("pheromone heatmap") plt.show()逻辑说明:cmap="hot"让高信息素区域偏亮,低信息素偏暗。路径用青色叠加,方便看路径是否落在高信息素带上。参数上,如果热力图整体偏暗,说明信息素累加不够,可以适当增大 Q 或减小 RHO。
另一个验证手段是多次运行取统计。蚁群算法有随机性,单次结果好不代表稳定。固定地图和参数,跑 10 次,记录最优长度和到达最优的迭代轮数。如果 10 次里有 8 次都能在 60 轮内收敛到接近最优,说明参数比较稳;如果波动很大,就回到第 3 章调 ρ 和 β。
我自己的习惯是:每次改完参数,先跑 3 次看趋势,再跑 10 次看稳定性,最后把信息素热力图和收敛曲线一起存档。这样下次换地图时,能快速判断是地图问题还是参数问题。踩过的坑告诉我,蚁群算法不怕参数多,怕的是只看一次结果就下结论。希望帮到你。
本文还有配套的精品资源,点击获取