基于Dijkstra与时间窗的AGV调度MATLAB实现与冲突避免 📅 发布时间:2026/9/13 6:04:41 👁 浏览次数: 简介基于时间窗规划与Dijkstra最短路径的AGV调度算法源码包专为智能制造、物流仓储场景下的自动导引车路径规划与避碰调度而设计。项目包含完整MATLAB实现与全部测试数据覆盖地图初始化、路径预规划、时间窗检测、路口冲突判定等关键模块适合自动化、电子信息、计算机等相关专业学生用于课程设计、毕业设计或算法验证。压缩包共17个文件其中以15个.m源码文件为主包含Get_TimerWindow、Detection_TW、planningPath、dijkstraR等核心函数另有README说明文档和开源许可证整包仅17KB结构紧凑、便于二次开发。资源发布后已有477人学习代码经测试运行成功、功能完善可直接运行查看调度效果也可在此框架上扩展多AGV协同、动态避障等高级功能。1. 用Dijkstra找路只是开始AGV调度真正的问题是时间窗规划一条环形产线上跑 20 台 AGV每台车都调 Dijkstra 算自己的最短路径看起来每台车都走对了路实际跑起来却在交叉口互相锁死、在同一个装卸站排队等 3 分钟。这不是地图建模的问题而是经典的“局部最优≠全局最优”Dijkstra 只回答了“空间上走哪条路”完全没有回答“时间上什么时候走”。时间窗规划做的就是把路径拆成带到达时刻和离开时刻的区间让多台 AGV 在空间和时间上错峰通过。这个标题对应的是一套在智能制造、仓储物流里非常常见的“路径规划交通管制”组合方案Dijkstra 负责离线算出候选路径时间窗负责在线维护每台车对节点和路段的占用情况检测到重叠就滑动、等待或重规划。本文用 MATLAB 从头实现这套逻辑覆盖邻接矩阵构建、dijkstra_agv 函数、时间窗预留与冲突检测、数据组织与参数设计最后给出验证和死锁处理手段。适合正在做 AGV 调度系统、物流仿真或者毕业设计里要完成“多车调度”部分的同学参考。2. 把路径装进时间轴AGV时间窗模型与冲突判定2.1 为什么单机最短路径在多车场景下会退化Dijkstra 给出的是一条静态最短路径它假设整条路上只有这一台车。多台 AGV 同时运行后路径之间会产生三种空间耦合共用同一交叉节点、在同一路段相向而行、在装卸站狭路相逢。任何一台车多停 5 秒都会沿路径向后传导最后表现出整个任务集的完工时间明显变长甚至出现闭环等待也就是死锁。在调度系统的设计上解决办法不是弃用 Dijkstra而是在它上面加一层“时间维坐标”。每台车沿 Dijkstra 给出的路径移动时把自己通过每个节点和每条边的时间区间记录下来。后续车辆在请求同一资源时先看自己的期望区间是否和已有区间重叠如果重叠就等待或绕行。这就是“时间窗规划”的思想也是这个标题里 Dijkstra 和时间窗必须配合使用的原因。2.2 节点时间窗与边时间窗数据结构怎么定实现时间窗之前要先明确一个粒度问题。把时间窗只建在节点上逻辑简单但无法描述两台车在同一路段上面对面行驶的场景把时间窗同时建在节点和边上可以覆盖全部冲突类型代价是数据量翻倍。常见做法是节点和边各维护一张时间窗表因为 AGV 的冲突大多发生在交叉口和装卸点而边上的对向冲突又必须单独判断。节点时间窗表的每一行描述一次资源占用字段设计如下字段含义示例node_id节点编号12start_time到达该节点的时间35.0end_time离开该节点的时间38.5agv_id占用该节点的AGV编号3task_id所属任务编号7边上时间窗表的结构基本一致只是主键换成 edge_id。边的时间窗需要额外考虑通行方向所以在数据表里用 start_node 和 end_node 作为边的唯一定位而不是只存一个边序号。MATLAB 实现时用结构体数组或者 cell 数组都可以但节点规模超过 200 个、AGV 数量超过 30 台时把时间窗按节点做索引会更方便也就是用一个 cell 数组tw_node{node_id}存放该节点下所有的占用区间。2.3 时间窗重叠判定与“窗口滑动”策略两个时间区间[a, b]和[c, d]重叠的条件是c b a d这是时间窗冲突检测的基础公式。假设已有车辆占用了节点 5 的区间[30, 35]新来车辆请求[32, 38]代入公式32 35 30 38条件成立说明冲突。此时简单粗暴地拒绝任务不可取工业上更常用的是“窗口滑动”把新车的进入时刻从 32 顺延到已有区间的结束时刻 35再看[35, 41]是否和下一个占用冲突直到找到一段完整空闲的窗口。窗口滑动的优点是不需要修改已有车辆的路径和时间窗后到车辆承担等待代价系统实现十分简单。缺点也很明显等待时间过长会拖慢整批任务。所以实际工程中会给等待设置上限比如 15 秒超过之后触发重规划或者改派另一条路径。这个阈值是后面参数调试章节的关键对象之一。2.4 调度主线的优先级策略时间窗分配的顺序就是调度优先级。最简单的是“任务发布时间先到先服务”按任务发布时间排序逐个做路径规划和时间窗预留。这种做法在小规模场景下表现不错代码也好写。但当高优先级任务被低优先级任务挡住时需要支持“挤占”也就是把低优先级任务的时间窗整体后移给高优先级任务腾窗口。挤占操作的实现是先从时间窗表里删除低优先级任务的所有区间按新顺序重新预留如果低优先级任务无法在截止时间内找到窗口再考虑重新规划路径。优先级策略的选用直接影响调度结果一般建议按照“紧急插单 按任务截止时间 按发布先后”的顺序设定不同行业侧重点不同这个在后面参数表里给出具体建议值。3. matlab实现邻接矩阵、dijkstra函数与时间窗预留3.1 数据文件怎么读nodes、edges、agv、tasks标题里“完整源码全部数据”说明数据文件是项目的一部分。常见的数据组织方式是项目根目录下的 data 文件夹里放 4 个 CSV 文件nodes.csv 存节点编号和坐标edges.csv 存边的起终点和通行时间agv.csv 存车辆速度与初始位置tasks.csv 存任务的装卸点与发布时间。MATLAB 里直接用 readtable 读取nodes readtable(data/nodes.csv); edges readtable(data/edges.csv); agvs readtable(data/agv.csv); tasks readtable(data/tasks.csv);读取之后要做两件事根据 edges 构建邻接矩阵把通行时间作为边的权值再根据 agvs 里记录的初始节点为每台车在时间窗表中插入一条“驻留区间”表示该车在 0 时刻之前已经占用了这个节点。节点数少的时候可以直接用双层循环填邻接矩阵节点超过 500 个建议改成 sparse 矩阵能显著降低 Dijkstra 的松弛时间。3.2 一个能跑的dijkstra_agv.mMATLAB 自带的shortestpath函数可以直接算最短路径但调度系统里经常需要在某个节点冲突时“临时封路”或者在一次计算里同时返回路径和距离。自己实现一个 Dijkstra便于在松弛循环里加自定义逻辑。以下是我常用的版本function [dist, path] dijkstra_agv(adj, start_node, goal_node) % dijkstra_agv: 经典Dijkstra最短路径算法 % 输入: adj 邻接矩阵, adj(i,j) 从i到j的通行时间, inf表示不可达 % start_node 起点节点编号 % goal_node 终点节点编号 % 输出: dist 起点到各节点的最短距离数组 % path 从起点到终点的节点编号序列, 找不到时为空 n size(adj, 1); dist inf(1, n); prev zeros(1, n); % prev(v) 记录路径上v的前驱节点 visited false(1, n); dist(start_node) 0; for iter 1:n unvisited_dist dist; unvisited_dist(visited) inf; % 屏蔽已访问节点 [min_dist, u] min(unvisited_dist); if isinf(min_dist) break; % 剩余节点不可达 end if u goal_node break; % 提前终止 end visited(u) true; for v 1:n if ~visited(v) isfinite(adj(u, v)) alt dist(u) adj(u, v); if alt dist(v) dist(v) alt; prev(v) u; end end end end path []; if isinf(dist(goal_node)) return; end % 从终点回溯得到路径 path goal_node; while path(1) ~ start_node path [prev(path(1)), path]; end end这段代码有一个 MATLAB 特有的细节min(unvisited_dist)返回的是逻辑数组中的下标由于被 inf 屏蔽的节点不会成为最小值除非所有节点都已访问所以可以直接当作节点编号使用。另一个细节是提前终止条件一旦当前最小距离节点已经是终点就可以退出循环不必遍历全图。路径回溯时用while path(1) ~ start_node逐次把前驱插到路径序列头部得到的就是从起点到终点的顺序数组。提示如果图里有负权边Dijkstra 不适用。AGV 地图的通行时间永远是正值所以这里不需要考虑 Bellman-Ford。3.3 时间窗预留函数如何找空洞、如何滑窗Dijkstra 给定路径后下一步是把路径上的每个节点和每条边都检查一遍时间窗。下面的函数实现单节点上的窗口预留“期望到达时刻”传入后如果与已有占用重叠就把期望到达时刻滑动到冲突区间的结束时刻循环直到找到空闲窗口。function [enter_t, exit_t, tw_list] reserve_node_time(tw_cell, node_id, agv_id, enter_t, occupy_t) % reserve_node_time: 在指定节点上预留时间窗 % tw_cell : cell数组, tw_cell{node_id} 是 Nx3 矩阵, 每行为 [start, end, agv_id] % node_id : 当前请求占用节点的编号 % agv_id : 请求车辆编号 % enter_t : 期望到达时刻 % occupy_t: 需要的占用时长 % 返回值 : 实际进入时刻, 离开时刻, 更新后的时间窗矩阵 tw tw_cell{node_id}; % 取出该节点已有的时间窗表 n_occ size(tw, 1); while true conflict false; for i 1:n_occ s0 tw(i, 1); e0 tw(i, 2); if enter_t e0 enter_t occupy_t s0 enter_t e0; % 窗口向后滑动 conflict true; break; end end if ~conflict break; end end exit_t enter_t occupy_t; tw [tw; enter_t, exit_t, agv_id]; tw_cell{node_id} tw; tw_list tw; end这个函数的核心逻辑在while true循环内部每次发现重叠就把进入时刻改成已有占用区间的结束时刻然后重新从头扫描所有区间。因为滑动之后的区间可能在更后面又和另一段占用重叠所以必须循环扫描直到完整扫完一遍都没有冲突为止。时间复杂度是 O(k^2) 级别k 是当前节点的占用区间数量AGV 数量在几十台量级时完全够用如果单节点占用区间超过上千再退化成二分查找或者用排序区间合并优化。3.4 调度主循环把路径和时间窗串起来有了路径函数和时间窗预留函数之后调度主循环就是把两者串起来。按照任务发布时间从早到晚排序逐个任务执行三条逻辑调用 dijkstra_agv 得路径沿路径逐段调用时间窗预留函数记录最终进入每个节点的时间。% 主循环伪代码: tasks 按 priority 排序后逐条调度 dispatch_time zeros(height(tasks), 1); for k 1:height(tasks) src tasks.start_node(k); dst tasks.dest_node(k); [dist, path] dijkstra_agv(adj, src, dst); if isempty(path) warning(任务%d无可行路径, tasks.id(k)); continue; end t_enter tasks.release_time(k); % 任务发布时刻作为初始时间 for p 1:length(path) - 1 node_now path(p); node_next path(p 1); travel_t adj(node_now, node_next); % 边通行时间 [t_enter, ~, ~] reserve_node_time(tw_node, node_next, ... k, t_enter travel_t, node_occupy_t); end dispatch_time(k) t_enter; end这段代码里有一个需要留意的建模假设把一条边的通行时间当作固定值不考虑加速度和减速过程。真实 AGV 在启动和刹车时速度不是恒定的但在一版可运行的调度仿真里先把车辆按匀速处理时间窗的差别体现在节点占用时间上通常给节点预留 2 到 5 秒来模拟过交叉口和停靠。加速度模型不是必须的后续如果要做高精度仿真可以在 t_enter travel_t 这一行加上驱动距离造成的额外时间。3.5 运行仿真与保存结果调度主循环跑完后MATLAB 工作区里会有每台车的路径数组和时间窗记录。保存结果时建议直接导出成两个 CSV路径表 route_result.csv 存车辆编号、节点顺序、进入时刻时间窗表 tw_result.csv 存节点编号、进入时刻、离开时刻、车辆编号。这样后续验证和可视化都不需要重新跑一遍仿真。TA_batch_result table(agv_ids, route_strings, enter_times, leave_times);writetable(TA_batch_result, route_result.csv);保存时还要把全部时间窗汇总成一张表便于检查是否有某个节点上的两个窗口重叠。这在第 5 章里会作为无冲突性验证的输入所以这一步不要省略。4. 数据怎么设计、参数怎么调从“能跑”到“结果可用”4.1 “全部数据”的生产方式一套调度算法能不能复现取决于数据是否可复现。小型场景可以手工构造6 个节点、8 条边、2 台 AGV、4 个任务每个任务是从某节点搬运到另一节点手工填 CS V 文件就行。但手工数据覆盖不了随机冲突所以更建议用脚本生成随机地图下面是生成随机无向图的简单方式% 生成 nodes 数量为 N 的随机地图, 边权表示通行时间 N 20; adj inf(N, N); % 先保证连通性: 生成一颗随机生成树 node_order randperm(N); for i 1:N-1 w 5 randi(10); % 通行时间 5~14 秒 adj(node_order(i), node_order(i1)) w; adj(node_order(i1), node_order(i)) w; end % 再补充额外边增加路网密度 for i 1:N for j i1:N if isinf(adj(i, j)) rand() 0.15 w 5 randi(10); adj(i, j) w; adj(j, i) w; end end end随机生成树保证任意两点连通额外边只会增加路径选择不会产生孤立点。任务数据的生成则要注意发布时间的分布全部同时发布会让时间窗在第一波就排满不能反映动态调度建议生成任务发布时间时错开比如用5 * rand()作为间隔模拟产线上陆续到达的搬运请求。这样调起来才能观察到时间窗滑动和重规划的实际效果。4.2 需要优先关注的参数表调度算法的行为很大程度上由参数决定以下参数表是我在类似场景下的默认值贴出来作为初跑版本参数含义建议初值说明node_occupy_t车辆通过节点占用时长3 s模拟交叉口减速和停靠agv_speedAGV匀速速度1.0 m/s用于把距离换算成时间max_wait_time冲突时最大等待时间15 s超过后触发重规划priority_mode调度优先级模式1按发布时间2按截止时间 3紧急插单replan_attempts最大重规划次数3超过则判定任务失败tw_slot时间窗最小粒度0.5 s用于取整避免浮点误差参数调整时建议一次只动一个。先调 node_occupy_t如果总完工时间明显偏高说明节点占用时长估大了再调 max_wait_time如果任务频繁触发重规划说明等待阈值设太短AGV 一碰到冲突就绕路整体路径发散。两轮之后再看 priority_mode只有多任务之间确实存在优先级差异时才需要换模式。4.3 结果可视化路径叠加图与AGV甘特图MATLAB 里可视化可以帮助快速确认路径和时间窗是否合理。路径叠加图的做法是把地图节点坐标画出来再用 plot 把每台车的路径序列连接起来figure; hold on; axis equal; plot(nodes.x, nodes.y, ko, MarkerSize, 8); % 节点 for i 1:length(agvs.id) pts route_cell{i}; % 第i台车的节点序列 plot(nodes.x(pts), nodes.y(pts), LineWidth, 1.5); end text(nodes.x, nodes.y, string(nodes.id), HorizontalAlignment, center);路径叠加图能发现节点绕路和路径交叉但看不出时间先后。想看时间冲突要用甘特图横轴是时间纵轴是节点编号每个时间窗画成一条横条重叠的部分一眼就能看到。MATLAB 可以用 rectangle 函数逐个绘制窗口矩形颜色按 AGV 编号区分。发现两个横条上下错位但时间上有重合区就是时间窗冲突需要回到第 3 章的预留逻辑排查。4.4 参数调试的顺序和方法参数调试的核心顺序是先用随机地图少任务量跑通观察是否有路径缺失再把任务量翻倍观察完工时间和死锁次数最后再把 max_wait_time 和 node_occupy_t 拉到极端值确认系统不会因为参数设置而崩溃。整个过程配合tic/toc记录调度主循环耗时MATLAB 的 profile 工具可以定位耗时集中在 dijkstra_agv 还是 reserve_node_time 上。提示参数调试阶段不要直接使用完整大数据集先在 20 节点、8 台车的规模下把逻辑跑稳再逐步扩大。AGV 调度优化是一个增量过程先从数据规模小、参数可解释的模型起步效果比一开始追求大规模仿真好得多。5. 验证、死锁规避与性能优化5.1 无冲突性验证扫描时间窗表调度结果是否满足时间窗约束可以用一段后置检查代码来确认把全部节点时间窗收集到一张大表里按 node_id 和 start_time 排序逐个检查同一节点内是否有区间重叠。tw_all sortrows(tw_result, {node_id, start_time}); for i 1:height(tw_all) - 1 if tw_all.node_id(i) tw_all.node_id(i 1) if tw_all.start_time(i) tw_all.end_time(i 1) error(节点%d存在时间窗重叠, tw_all.node_id(i)); end end end这段检查脚本应该作为调度主程序的固定收尾。只要检查通过就能确认所有车辆在空间和时间上都没有抢占同一节点。只有通过这一关才有资格进一步讨论优化。5.2 死锁规避三步处理时间窗预留并不能完全消灭死锁。两台 AGV 面对面堵在一条窄道各自的时间窗可能因为滑动而互相推延最终形成等待环。常见处理分三步等到超过 max_wait_time 则判定疑似死锁将该路段在邻接矩阵中临时设为 inf相当于封路重新调用 dijkstra_agv 计算绕行路径。% 死锁重规划: 把冲突边临时封禁 adj_temp adj; adj_temp(node_a, node_b) inf; adj_temp(node_b, node_a) inf; % 无向边双向封禁 [dist_new, path_new] dijkstra_agv(adj_temp, agv_curr_node, agv_goal_node);封路重规划可以突破空间上的僵局但如果所有绕行路径都已尝试过任务应判定失败并上报而不是无限等待。更高阶的做法是设计单向环线地图或者设置避让区让对向冲突在路网结构上就不可能出现这对产线规划阶段非常有价值。5.3 让MATLAB调度跑得更快规模变大后MATLAB 的性能瓶颈集中在三个地方Dijkstra 的内层循环、时间窗预留的窗口扫描、以及结果保存时反复动态增长数组。对应的优化手段也很明确邻接矩阵用 sparseDijkstra 循环里用逻辑索引替代逐个遍历时间窗预留尽量把区间按 start_time 排序后做二分查找。此外主循环里尽量预分配所有结果数组或者用 cell 数组等待循环结束后一次性合并避免在循环内不断writetable。这些改动都不会影响算法结果只影响运行时长。另一个生产级优化方向是把 MATLAB 中的时间窗预留算法用 C Mex 重写或者在仿真层直接导出路径后再用 Python 做批处理优化。对一版以验证和演示为主要目标的项目来说先完成上述三个 MATLAB 层面的优化已经足够跑 200 个节点、50 台车、500 个任务的场景优化前后耗时差距通常在 5 倍以上。本文还有配套的精品资源点击获取