MATLAB最短路算法实战:从Dijkstra原理到美赛建模应用
2026/9/8 4:20:36 网站建设 项目流程

1. 从“最短路”到“美赛”:一个数学建模新手的必经之路

如果你正在准备美国大学生数学建模竞赛(MCM/ICM),并且把目光投向了“最短路问题”,同时还在自学MATLAB,那么恭喜你,你正踩在一条非常经典且高效的备赛路径上。这条路,我走过,也带很多学生走过。最短路问题,几乎是数模竞赛中“出场率”最高的几类问题之一,从物流配送、网络通信、交通规划到灾害应急,它的身影无处不在。而MATLAB,以其强大的矩阵运算能力和丰富的工具箱,是解决这类问题当之无愧的“瑞士军刀”。

但问题来了:网上教程千千万,为什么自己动手还是“一看就会,一写就废”?很多同学在自学时,往往陷入两个极端:要么沉迷于各种炫酷算法的理论推导,却写不出能跑的代码;要么只会调用几个内置函数,一旦题目条件稍有变化,就束手无策。这篇文章,我想和你分享的,不是一份冰冷的算法说明书,而是一个从“知道概念”到“能在美赛高压下灵活运用”的完整实战指南。我们会把最短路问题掰开揉碎,结合MATLAB,讲清楚为什么要这么建模,怎么用代码实现,以及在实际比赛中可能遇到哪些坑。我们的目标很明确:让你手里有代码,心里有地图,面对美赛题目时,能迅速找到那条通往解决方案的“最短路”。

2. 最短路问题:不止于“找最短距离”

在深入代码之前,我们必须先统一思想:数学建模中的“最短路”,绝不仅仅是地图上两点之间的最短直线距离。它是一种抽象的优化思想,核心是在一个由“节点”和“边”构成的网络(图)中,寻找从起点到终点总“权重”最小的路径。

2.1 核心概念与美赛场景映射

这里有几个关键概念,你需要像熟悉自己名字一样熟悉它们:

  • 节点:可以代表任何实体。在城市交通网络中是交叉路口;在通信网络中是路由器或基站;在物流问题中是仓库、配送中心或客户点;在社交网络中是个人
  • :连接两个节点的关系,具有方向性(有向图)或无方向性(无向图)。在美赛题目中,一条边可能代表:
    • 道路:权重是距离、时间或通行成本。
    • 数据传输链路:权重是带宽、延迟或丢包率。
    • 物资运输通道:权重是运输成本、碳排放量或风险值。
    • 人际关系:权重是亲密度、信息传播概率。
  • 权重:这才是最短路问题的灵魂。它定义了“短”的标准。在很多赛题中,目标不是最小化地理距离,而是最小化时间、成本、风险或最大化可靠性(此时可将可靠性倒数作为权重)。例如,在灾害救援题中,路径的“权重”可能需要综合道路损坏程度、通行时间和救援紧迫性。

一个常见的建模陷阱:很多新手会不假思索地把题目给出的“距离”直接当作权重输入算法。但美赛的精华往往在于权重的自定义。你需要根据题目目标,构建一个复合权重函数。比如,权重 = a * 时间 + b * 成本 + c * 风险,其中a, b, c是根据题目要求设定的系数。这个构建过程本身,就是建模能力的体现。

2.2 主流算法选型:为什么是Dijkstra和它的朋友们?

面对最短路问题,MATLAB提供了几种武器。选择哪一种,取决于你的图(网络)的特点:

  1. graph/digraph对象 +shortestpath函数:这是MATLAB R2015b以后推荐的主流、高级且直观的方法。它底层封装了成熟的算法,你只需要关心图的构建。对于99%的美赛问题,这应该是你的首选。因为它代码简洁,易于调试,且能方便地输出路径节点序列和总权重。
  2. graphallshortestpaths函数:用于计算图中所有节点对之间的最短路径(即“全源最短路”),返回一个距离矩阵。当你需要分析网络整体连通效率,或起点、终点不确定需要频繁查询时有用。例如,评估一个交通网络中所有区域之间的平均通行时间。
  3. 自定义实现Dijkstra或A*算法:虽然不推荐在时间紧迫的美赛中从头造轮子,但理解它们至关重要。
    • Dijkstra算法:解决非负权重单源最短路问题的经典算法。它是理解最短路思想的基石。在MATLAB中,当你的权重非常复杂(例如是其他变量的函数),无法直接构建graph对象时,你可能需要手动实现Dijkstra来嵌入你的权重计算逻辑。
    • A*算法:在Dijkstra基础上加入了“启发式函数”,用于预测当前节点到终点的代价,从而大幅减少搜索范围,效率更高。特别适用于已知终点位置、且节点具有地理位置信息的问题,比如网格地图上的寻路。MATLAB的shortestpath函数在某些情况下对某些图类型可能就使用了A*的变种。

选型心法:优先使用graph+shortestpath组合。只有当你的权重是动态的、需要复杂计算,或者题目明确要求你实现特定算法时,才考虑手动编写。在美赛论文中,即使你调用了内置函数,也必须在模型部分清晰地阐述你使用的是Dijkstra算法原理,并说明MATLAB的实现是可靠且高效的。

3. MATLAB实战:从构建网络到求解路径

理论说得再多,不如一行代码。让我们从一个完整的、可复用的例子开始。假设我们遇到一个简化版的美赛题目:某城市有5个主要区域(节点),部分道路单行(有向边),我们需要找出从区域1(市政府)到区域5(应急物资中心)的最短时间路径。已知某些道路在高峰时段会有拥堵,时间权重需要调整。

3.1 基础建模:构建图对象并求解

这是最标准、最推荐的操作流程。

% 步骤1:定义节点和边 % 节点就是1,2,3,4,5,我们更关心的是边和权重 start_nodes = [1, 1, 2, 2, 3, 4]; % 每条边的起点 end_nodes = [2, 3, 3, 4, 5, 5]; % 每条边的终点 % 权重:基于平时通行时间(分钟) weights = [5, 10, 3, 2, 7, 4]; % 步骤2:创建有向图对象 G = digraph(start_nodes, end_nodes, weights); % 步骤3:绘制图形,直观检查(论文中可放入美观的示意图) figure; p = plot(G, 'EdgeLabel', G.Edges.Weight, 'LineWidth', 2, 'MarkerSize', 7, 'NodeColor', 'r'); title('城市区域交通网络图(平时)'); xlabel('节点:1-市政府, 5-应急中心'); % 步骤4:计算从节点1到节点5的最短路径 [path_nodes, path_length] = shortestpath(G, 1, 5); fprintf('最短路径节点序列:%s\n', num2str(path_nodes)); fprintf('最短通行时间:%.2f 分钟\n', path_length); % 步骤5:高亮显示最短路径 highlight(p, path_nodes, 'EdgeColor', 'g', 'LineWidth', 3); highlight(p, path_nodes(1), 'NodeColor', 'b', 'MarkerSize', 10); % 起点蓝色 highlight(p, path_nodes(end), 'NodeColor', 'm', 'MarkerSize', 10); % 终点洋红色

代码解读与避坑点

  • digraph用于创建有向图。如果是双向道路,你需要添加两条方向相反的边。使用graph则是无向图。
  • shortestpath函数返回两个值:path_nodes是路径经过的节点编号数组,path_length是路径的总权重(时间)。这是你论文中可以直接引用的结果。
  • 易错点:节点编号必须是正整数,但可以不连续。如果数据中节点是字符串(如地名),可以使用cell数组,G = digraph({'A','B'}, {'B','C'}, weights)shortestpath函数同样支持。
  • 可视化:在美赛论文中,一张清晰的网络图和最短路径高亮图是极大的加分项。务必调整好图形属性,使其在论文中美观、易读。

3.2 进阶操作:动态权重与全源最短路

现在,考虑题目进阶:晚高峰时段,道路(1,3)和(3,5)的通行时间增加50%。我们需要重新计算。

% 假设我们有一个函数,能根据道路ID和时间段返回动态权重 % 这里简化处理,直接修改权重向量 peak_weights = weights; % 复制原权重 peak_weights([2, 5]) = weights([2, 5]) * 1.5; % 道路(1,3)和(3,5)拥堵 % 创建高峰期的图对象 G_peak = digraph(start_nodes, end_nodes, peak_weights); % 重新计算最短路 [path_nodes_peak, path_length_peak] = shortestpath(G_peak, 1, 5); fprintf('高峰时段最短路径:%s, 时间:%.2f 分钟\n', num2str(path_nodes_peak), path_length_peak); % 对比分析 if ~isequal(path_nodes, path_nodes_peak) fprintf('注意:高峰时段最优路径发生了变化!\n'); % 可以进一步分析变化的原因,这是论文中可以进行深入讨论的点 end

动态权重处理心得:在实际美赛题目中,权重可能依赖于时间、流量、天气等多种因素。我的建议是,将权重计算封装成一个独立的函数。例如,getEdgeWeight(edge_id, current_time, traffic_flow)。然后在构建图之前,循环所有边,调用这个函数生成权重数组。这样代码结构清晰,也便于进行灵敏度分析(比如改变某个参数,看最短路径是否稳定)。

接下来,如果题目要求你评估整个网络的效率,或者需要频繁查询任意两点间的最短时间(例如为多个救援队规划路线),就需要计算全源最短路。

% 计算所有节点对之间的最短距离矩阵 D = distances(G); % D是一个 n x n 的矩阵,D(i,j) 表示节点i到j的最短距离 % 例如,计算网络的平均最短通行时间(忽略无穷大,即不连通的点) valid_distances = D(isfinite(D)); % 取出所有有限值 avg_time = mean(valid_distances); fprintf('网络平均最短通行时间:%.2f 分钟\n', avg_time); % 查找哪两个区域之间最“远” [max_dist, idx] = max(D(:)); [row, col] = ind2sub(size(D), idx); fprintf('最不连通的区域对是 %d -> %d, 距离为 %.2f 分钟\n', row, col, max_dist);

distances函数返回的矩阵D是一个强大的中间结果。你可以基于它做很多网络分析,比如计算网络的直径max(D(:)))、平均路径长度、或某个节点的中心性(到所有其他节点距离的平均值)。这些指标都能为你的模型提供更丰富的分析和讨论维度。

4. 当内置函数不够用:手撕Dijkstra算法

虽然内置函数强大,但总有特殊情况。比如,你的“权重”不是预先知道的固定值,而是在路径搜索过程中,根据已经走过的路径动态计算出来的(例如,路径的“风险”会累积)。这时,你可能需要手动实现算法核心。理解Dijkstra,也能让你在论文中把算法讲得更透彻。

下面是一个针对节点编号为1到n的网络的、非最优但非常清晰易懂的Dijkstra实现,适合教学和理解。

function [dist, prev] = myDijkstra(adj_matrix, source) % MYDIJKSTRA 使用邻接矩阵实现Dijkstra算法 % adj_matrix: n x n 的邻接矩阵,adj_matrix(i,j) 表示从i到j的边的权重,若无连接则为Inf % source: 源节点编号 % dist: 1 x n 向量,dist(i) 表示从源点到节点i的最短距离 % prev: 1 x n 向量,prev(i) 表示节点i在最短路径上的前驱节点,用于回溯路径 n = size(adj_matrix, 1); dist = inf(1, n); prev = zeros(1, n); visited = false(1, n); dist(source) = 0; for i = 1:n % 找到未访问节点中距离最小的节点u min_dist = inf; u = -1; for v = 1:n if ~visited(v) && dist(v) < min_dist min_dist = dist(v); u = v; end end if u == -1 % 所有可达节点都已处理完毕 break; end visited(u) = true; % 松弛操作:更新u的所有邻居的距离 for v = 1:n if adj_matrix(u, v) < inf % 如果u到v有边 alt = dist(u) + adj_matrix(u, v); if alt < dist(v) dist(v) = alt; prev(v) = u; end end end end end % 使用示例:将之前的图G转换为邻接矩阵 adj = full(adjacency(G, 'weighted')); % 将graph对象转换为带权邻接矩阵,无边处为0 adj(adj == 0) = inf; % 将0权重(表示无边)替换为Inf for i = 1:size(adj,1) adj(i,i) = 0; % 对角线设为0 end [dist, prev] = myDijkstra(adj, 1); fprintf('手动Dijkstra结果,到节点5的距离:%.2f\n', dist(5)); % 回溯路径 path = []; node = 5; while node ~= 0 path = [node, path]; node = prev(node); end fprintf('回溯路径:%s\n', num2str(path));

为什么需要自己实现?

  1. 教学与理解:这段代码清晰地展示了Dijkstra算法的两个核心步骤:选择未访问的最小距离节点松弛操作。在论文的算法描述部分,你可以直接引用这个逻辑。
  2. 处理动态权重:在松弛操作部分(alt = dist(u) + adj_matrix(u, v)),这里的adj_matrix(u, v)可以替换为一个函数调用,比如getDynamicWeight(u, v, dist(u), path_so_far),从而实现权重依赖于已走路径的复杂场景。
  3. 定制化输出:你可以轻松修改代码来记录更多信息,比如在搜索过程中探索了哪些节点,用于算法效率分析或绘图。

性能提醒:上述实现使用简单的循环查找最小节点,时间复杂度为O(n^2),对于节点数n很大的图(比如上千个节点)会很慢。美赛中如果遇到大规模网络,应优先使用内置的shortestpath函数,它经过了高度优化。自己实现的Dijkstra主要用于小规模问题或原理演示。

5. 美赛实战中的典型问题与建模技巧

掌握了基础工具,我们来看看在真实的72小时美赛战场上,最短路问题会以怎样的面貌出现,以及如何应对。

5.1 多目标优化与K最短路径

很多时候,“最短”不是唯一标准。题目可能要求“在时间不超过T的前提下,成本最低的路径”,或者“寻找前3条备选路径以供决策”。这就需要用到K最短路径算法。

MATLAB没有直接提供K最短路径函数,但我们可以通过一些技巧来近似实现,或者使用Yen's algorithm的思想。一个实用的比赛技巧是:使用shortestpath并修改图结构

思路:找到第一条最短路径后,依次“删除”这条路径上的某条边(或将权重设为无穷大),然后重新计算最短路,得到的新路径就是一条不同的、通常较长的路径。重复这个过程,可以得到一组备选路径。

% 假设我们想找从1到5的前3条最短路径 G = digraph(start_nodes, end_nodes, weights); paths = cell(1, 3); path_lengths = zeros(1, 3); G_temp = G; for k = 1:3 try [path_nodes, path_len] = shortestpath(G_temp, 1, 5); paths{k} = path_nodes; path_lengths(k) = path_len; % 如果不是最后一条,则“破坏”当前找到的路径,迫使算法寻找下一条 if k < 3 % 简单策略:移除当前路径的第一条边 edge_to_remove = findedge(G_temp, path_nodes(1), path_nodes(2)); if edge_to_remove ~= 0 % 将该边权重设为Inf,相当于移除 G_temp.Edges.Weight(edge_to_remove) = inf; else % 如果是无向图或者边不存在,可能需要更复杂的逻辑 break; end end catch ME % 如果找不到路径,跳出循环 fprintf('只找到 %d 条路径。\n', k-1); paths(k:end) = []; path_lengths(k:end) = []; break; end end % 显示结果 for i = 1:length(paths) fprintf('第%d条路径: %s, 长度: %.2f\n', i, num2str(paths{i}), path_lengths(i)); end

注意:这种方法得到的“第K短”路径不一定严格是全局第K短,但通常是有效的、不同的路径,在美赛的实用场景中经常够用。在论文中,你需要说明这种方法是一种启发式方法,用于生成可行的备选方案集。

5.2 处理大规模网络与稀疏矩阵

美赛有时会提供真实的地理数据,节点数可能成千上万(如所有城市交叉口)。直接用邻接矩阵会消耗巨大内存。此时,必须利用图的稀疏性。

MATLAB的graph/digraph对象天生就为稀疏存储设计。当你用边的列表(start_nodes,end_nodes,weights)创建图时,它内部就是以高效的方式存储的。这是你无需担心底层优化的福利。

但是,如果你需要自己操作邻接矩阵,一定要使用稀疏矩阵

% 假设有上万个节点,但每个节点只连接少量邻居 n = 10000; % 随机生成一个稀疏的边列表(示例) s = randi(n, 100000, 1); % 10万条边的起点 t = randi(n, 100000, 1); % 终点 w = rand(100000, 1)*100; % 权重 % 创建稀疏邻接矩阵 (这是存储大规模图的正确方式) adj_sparse = sparse(s, t, w, n, n); % 使用graph对象(它处理稀疏矩阵很高效) G_large = digraph(adj_sparse); % 计算最短路 - 对于大规模图,这一步可能较慢,但MATLAB已优化 tic; [path, len] = shortestpath(G_large, 1, 5000); toc; fprintf('大规模图计算耗时:%.2f 秒\n', toc);

关键建议:在美赛中,如果遇到大规模数据,在论文里一定要提及你使用了稀疏存储技术来保证模型的可行性和计算效率,这是一个重要的建模细节。

5.3 将最短路嵌入更大模型:以物流配送为例

最短路很少是问题的终点,它常常是一个更大优化模型的子模块。例如,经典的车辆路径问题:有多个配送点、多辆车,如何规划路线使总成本最低?

在这种情况下,最短路算法扮演了“成本计算器”的角色。你需要预先计算所有配送点两两之间的最短距离(或时间、成本),形成一个距离矩阵(就是我们前面用distances函数得到的D矩阵)。然后,VRP模型会基于这个距离矩阵,去决定车辆的访问顺序。

建模流程

  1. 数据预处理:将地图抽象为网络图,节点包括仓库和所有客户点。
  2. 成本矩阵生成:使用distances(G)计算所有节点对之间的最短路成本,得到矩阵C,其中C(i,j)表示从点i到点j的最小成本。
  3. 构建VRP模型:将C矩阵作为输入,建立整数规划或启发式算法(如遗传算法、模拟退火)模型,决策变量是车辆的访问序列。
  4. 模型求解与验证:求解VRP模型后,得到的是节点的访问顺序。你需要将这个顺序映射回实际的路径上,这时可以再次调用shortestpath函数,画出每辆车的具体行驶路线。

在论文中,你需要清晰地阐述这两个阶段的耦合关系:“我们首先建立了交通网络的最短路模型,用于精确计算任意两点间的运输成本,生成了成本矩阵。随后,该成本矩阵作为核心输入,被用于后续的车辆路径优化模型中。”

6. 论文写作要点与代码整合策略

最后,也是最关键的一步:如何将你的MATLAB工作和思考,转化成一篇专业的美赛论文。

6.1 模型描述部分

不要写“我们使用了MATLAB的shortestpath函数”。要这样写:

“针对网络中的路径优化问题,本研究采用Dijkstra算法求解单源最短路径。该算法通过迭代更新从源点到其他所有节点的当前已知最短距离,最终保证找到全局最优解。其核心步骤包括初始化、选择未访问的最小距离节点、松弛操作等。在本模型中,边的权重定义为[根据你的问题定义,如:通行时间、物流成本等]。算法的具体实现基于MATLAB R2023a环境,该环境提供了经过高度优化的图论计算工具箱,确保了模型求解的效率和稳定性。”

6.2 结果可视化与呈现

一图胜千言。除了之前提到的网络图和高亮最短路径,你还可以:

  • 绘制路径对比图:将不同方案(如平时vs高峰)的最短路径在同一张底图上用不同颜色画出。
  • 绘制距离矩阵热图:使用imagesc(D)heatmap函数,直观展示网络中所有点对之间的最短距离,可以发现哪些区域是交通枢纽或孤岛。
  • 制作动画:如果你的模型与时间有关(如动态拥堵),可以制作路径随时间变化的动画,并导出为GIF或视频插入论文附录,这是极大的亮点。
% 示例:绘制距离矩阵热图 figure; imagesc(D); colorbar; title('所有区域对之间的最短通行时间矩阵'); xlabel('目标区域'); ylabel('出发区域'); % 可以进一步添加刻度,使坐标轴显示区域编号

6.3 代码整理与附录

美赛论文需要提交完整的代码。你的代码应该:

  1. 模块化:将不同的功能封装成函数,如createNetwork.m,calculateShortestPath.m,plotResults.m。主脚本main.m清晰简洁,按顺序调用这些函数。
  2. 注释清晰:关键步骤、复杂逻辑、模型假设都要有注释。用英文注释是加分项。
  3. 数据分离:将原始数据(节点、边、权重)放在单独的.mat文件或.csv文件中,通过loadreadtable读取。这使代码更易读,也便于更换数据测试。
  4. 附录展示:在论文附录中,不要粘贴全部代码,只粘贴核心算法的实现片段(如你自定义的Dijkstra函数)和主程序的框架。并说明完整代码已随论文提交。

自学MATLAB解决美赛最短路问题,是一个从工具使用到模型思维升华的过程。它始于一行shortestpath代码,但远不止于此。真正的挑战在于,如何将杂乱的现实问题抽象成一个清晰的网络图,如何定义合理的权重以反映题目核心目标,以及如何将最短路这个“零件”巧妙地嵌入到更大的解决方案“机器”中。我见过太多队伍止步于调用函数得出一个数字,而忽略了背后的建模艺术。记住,在美赛评委眼里,一个考虑了动态权重、进行了灵敏度分析、并提供了清晰可视化结果的“最短路”模型,远比一个单纯跑出了最小值的模型更有价值。这其中的差距,就是那需要你自己去思考和填补的、从“会用”到“精通”的“最短路”。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询