ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Dijkstra与Floyd算法:图论最短路径核心原理与应用实战

Dijkstra与Floyd算法:图论最短路径核心原理与应用实战 1. 从地图导航到网络规划最短路径算法的现实意义我们每天都在不自觉地使用最短路径算法。当你打开手机地图App输入起点和终点App在几秒钟内为你规划出一条耗时最短或距离最短的路线时背后就是最短路径算法在默默工作。这不仅仅是地图导航的专利从物流公司的配送路线优化、通信网络的数据包路由到社交网络中计算两个人的“六度空间”关系甚至是游戏里NPC的智能寻路其核心问题都可以抽象为在一个由“点”和“线”构成的“图”中如何高效地找到连接两个特定点的最优路径。这就是图论中“最短路径问题”的魅力所在。它剥离了现实世界的复杂表象将道路、路由器、人物、任务节点抽象为“顶点”将它们之间的连接关系如公路、光纤、社交关系、依赖关系抽象为“边”并为每条边赋予一个“权值”如距离、时间、成本、带宽。我们的目标就是在这样的加权图中找到从源点到目标点总权值最小的那条路径。在数学建模竞赛中无论是交通流优化、应急设施选址还是通信网络设计、项目关键路径分析只要问题涉及“最优连接”或“最小成本”最短路径算法几乎都是你必须掌握的建模工具箱中的核心组件。今天我们就深入探讨两种最经典、应用最广泛的最短路径算法Dijkstra算法和Floyd算法。我不会只停留在教科书式的步骤描述上而是会结合建模实战中的常见场景拆解它们背后的核心思想、适用边界、代码实现中的关键细节以及那些容易让新手栽跟头的“坑”。理解它们你不仅能解决“怎么走”的问题更能深刻理解“为什么这样走最好”从而在建模时灵活选用甚至进行算法改良以适应你的特定问题。2. 图与网络建模世界的通用语言在深入算法之前我们必须统一“语言”。图论为我们提供了一套强大的建模工具能将千变万化的实际问题转化为可计算、可分析的数学模型。2.1 图的基本概念与建模映射一个图G通常由两部分组成顶点集合V和边集合E记作G(V, E)。在建模时关键的一步是完成从现实对象到图元素的准确映射。顶点代表我们研究系统中的实体或状态。例如交通网络每个十字路口、交通枢纽是一个顶点。通信网络每台路由器、交换机或服务器是一个顶点。社交网络每个人或组织是一个顶点。项目计划每个子任务或里程碑是一个顶点。边代表实体之间的关系或连接。边可以是有向的箭头表示单向关系如单行道、任务依赖或无向的双向关系如普通公路、友谊关系。有向图边(u, v)和(v, u)是两条不同的边。适用于建模非对称关系。无向图边(u, v)等同于边(v, u)。适用于建模对称关系。权值附着在边上的一个数值用于量化“通过”这条边的代价。这是最短路径问题的核心。权值可以是距离/长度地理距离。时间通行时间、处理延时。成本过路费、运输成本。容量/可靠性的倒数有时我们希望找最可靠的路径可以将每条边的失效概率转化为“风险权值”。注意权值必须为非负值这是Dijkstra算法正确性的前提。如果存在负权边则需要使用Bellman-Ford等能处理负权的算法。在大多数实际建模场景如距离、时间、成本中非负假设是合理的。2.2 图的计算机表示邻接矩阵与邻接表要让计算机处理图我们需要将其数据结构化。两种最常用的表示方法是邻接矩阵和邻接表它们各有优劣选择哪一种会直接影响算法的效率。1. 邻接矩阵用一个二维数组matrix表示。matrix[i][j]的值表示从顶点i到顶点j的边的权值。如果i和j不相连则用一个特殊值表示如无穷大INF。优点直观检查任意两个顶点间是否有边、权值多少时间复杂度是 O(1)。缺点占用空间大为 O(|V|²)。对于顶点数很多但边很稀疏的图即每个顶点只连接少量其他顶点空间浪费严重。适用场景稠密图或者需要频繁查询任意两点间关系的场景。Floyd算法就天然适合用邻接矩阵实现。# 一个包含4个顶点的图的邻接矩阵示例 (无向图) INF float(inf) V 4 graph_matrix [ [0, 2, INF, 1], # 顶点0到0、1、2、3的权值 [2, 0, 3, INF], # 顶点1到0、1、2、3的权值 [INF, 3, 0, 4], # 顶点2 [1, INF, 4, 0] # 顶点3 ] # 解读顶点0和1之间有一条权值为2的边0和3之间权值为10和2之间没有直接边(INF)。2. 邻接表为每个顶点维护一个列表存储与该顶点直接相连的所有邻接顶点及对应边的权值。通常使用字典或数组套列表实现。优点空间效率高为 O(|V| |E|)。特别适合稀疏图。缺点查询任意两点间是否有边需要遍历列表最坏情况 O(|V|)。适用场景绝大多数实际情况下的图尤其是社交网络、交通网络每个路口通常只连接3-4条路等稀疏图。Dijkstra算法的优先队列实现通常基于邻接表。# 使用字典列表表示同一个图的邻接表 graph_adj_list [ {1: 2, 3: 1}, # 顶点0的邻居顶点1(权2), 顶点3(权1) {0: 2, 2: 3}, # 顶点1的邻居顶点0(权2), 顶点2(权3) {1: 3, 3: 4}, # 顶点2的邻居顶点1(权3), 顶点3(权4) {0: 1, 2: 4} # 顶点3的邻居顶点0(权1), 顶点2(权4) ]在数学建模中根据问题规模和数据特点选择合适的数据结构是第一步。通常如果顶点数超过1000且图比较稀疏邻接表是更优的选择。3. Dijkstra算法单源最短路径的经典解法Dijkstra算法由荷兰计算机科学家Edsger W. Dijkstra于1956年提出用于解决单源最短路径问题即从一个固定的源点出发计算它到图中所有其他顶点的最短路径和距离。3.1 算法核心思想贪心策略与逐步逼近Dijkstra算法的思想非常直观类似于“水波扩散”或“步步为营”。它维护两个集合已确定最短路径的顶点集合S算法开始时这个集合只有源点自己。我们确信已经找到了从源点到这些点的最短路径。未确定最短路径的顶点集合Q其他所有顶点。算法的核心是一个贪心选择在每一轮中从集合Q中选出当前距离源点最近的那个顶点u这个距离是基于当前已知信息估算的不一定是最终最短距离将其加入集合S。然后进行松弛操作检查顶点u的所有邻居v看看如果通过u中转能否缩短源点到v的当前已知距离。如果能就更新v的距离。为什么这个贪心策略是正确的关键在于所有边的权值都为非负。因为权值非负所以从源点直接到某个点的距离不可能比先绕到一个更远的点再到这个点的距离更短。这就保证了“当前距离源点最近的点其距离不可能再被其他路径更新得更小”因此可以放心地将其标记为已确定。3.2 算法步骤详解与手动演算让我们通过一个具体例子手动走一遍Dijkstra算法的流程这比看伪代码更能加深理解。假设我们有如下有向图求从顶点A到所有其他顶点的最短路径。顶点: A, B, C, D, E 边及其权值: A - B: 10 A - C: 3 B - C: 1 B - D: 2 C - B: 4 C - D: 8 C - E: 2 D - E: 7 E - D: 9我们使用两个数组来记录关键信息dist[]: 记录从源点A到每个顶点的当前最短距离估计值。初始化时dist[A]0其他为无穷大(INF)。visited[](或集合S): 标记顶点是否已确定最短路径。初始化:dist [A:0, B:INF, C:INF, D:INF, E:INF],visited {}第一轮:从未访问顶点中找dist最小的点是A (dist0)。将A标记为已访问visited {A}。松弛A的邻居B和C。A-B:dist[B] min(INF, 010) 10A-C:dist[C] min(INF, 03) 3更新后dist [A:0, B:10, C:3, D:INF, E:INF]第二轮:未访问顶点中dist最小的是C (dist3)。标记C为已访问visited {A, C}。松弛C的邻居B, D, E。C-B:dist[B] min(10, 34) 7(更新发现了一条更短路径A-C-B距离7)C-D:dist[D] min(INF, 38) 11C-E:dist[E] min(INF, 32) 5更新后dist [A:0, B:7, C:3, D:11, E:5]第三轮:未访问顶点中dist最小的是E (dist5)。标记E为已访问visited {A, C, E}。松弛E的邻居D。E-D:dist[D] min(11, 59) 11(未更新)dist不变。第四轮:未访问顶点中dist最小的是B (dist7)。标记B为已访问visited {A, C, E, B}。松弛B的邻居C, D。B-C: C已访问跳过。B-D:dist[D] min(11, 72) 9(更新)更新后dist [A:0, B:7, C:3, D:9, E:5]第五轮:最后一个未访问顶点D (dist9)。标记D为已访问visited {A, C, E, B, D}。松弛D的邻居E (已访问跳过)。最终结果: 从A到各点的最短距离A:0, B:7, C:3, D:9, E:5。 路径可以通过在松弛时记录“前驱节点”来回溯得到。例如到D的最短路径是A-C-B-D距离9。3.3 代码实现与优先队列优化基础的Dijkstra实现需要每次从未访问集合中查找dist最小的顶点这需要O(|V|)的时间加上外层循环O(|V|)总时间复杂度为O(|V|²)。这在顶点数多时效率很低。优化关键使用优先队列通常是最小堆。我们可以将未访问顶点按其当前的dist值放入最小堆中这样每次获取距离最小的顶点只需要O(log|V|)的时间。import heapq def dijkstra(graph_adj_list, start): 使用优先队列优化的Dijkstra算法 :param graph_adj_list: 邻接表表示的图graph_adj_list[u] [(v, weight), ...] :param start: 源点索引 :return: dist列表记录从start到每个点的最短距离 n len(graph_adj_list) dist [float(inf)] * n dist[start] 0 # 优先队列元素为 (当前距离, 顶点) pq [(0, start)] while pq: current_dist, u heapq.heappop(pq) # 如果当前取出的距离大于记录的距离说明是旧数据跳过 if current_dist dist[u]: continue # 遍历邻居 for v, weight in graph_adj_list[u]: new_dist current_dist weight if new_dist dist[v]: dist[v] new_dist heapq.heappush(pq, (new_dist, v)) return dist # 示例构建前面例子的邻接表 graph [ [(1, 10), (2, 3)], # A:0 - (B:1,10), (C:2,3) [(2, 1), (3, 2)], # B:1 - (C:2,1), (D:3,2) [(1, 4), (3, 8), (4, 2)], # C:2 - (B:1,4), (D:3,8), (E:4,2) [(4, 7)], # D:3 - (E:4,7) [(3, 9)] # E:4 - (D:3,9) ] print(dijkstra(graph, 0)) # 输出从A(0)出发的最短距离 # 预期输出: [0, 7, 3, 9, 5]时间复杂度使用优先队列后每个顶点和每条边最多被处理一次堆操作是O(log|V|)因此总时间复杂度为O((|V||E|) log |V|)。对于稀疏图这比O(|V|²)快得多。实操心得在建模编程实现时务必使用优先队列版本。这是面试和竞赛中的标准写法。另外注意代码中的if current_dist dist[u]: continue这一行至关重要。因为一个顶点可能被多次加入堆每次距离更新时但只有最早弹出的即距离最小的那次是有效的后续弹出的都是“过时”数据必须跳过否则会浪费大量时间。4. Floyd算法多源最短路径的动态规划方案Dijkstra算法解决了单源问题但如果我们需要计算图中任意两个顶点之间的最短路径呢一个朴素的想法是对每个顶点都运行一次Dijkstra时间复杂度为O(|V| * (|V||E|) log |V|)。对于稠密图|E|接近|V|²这约等于O(|V|³ log |V|)。而Floyd-Warshall算法简称Floyd算法提供了一种更简洁、在稠密图上有时更高效的多源最短路径解决方案其时间复杂度稳定为O(|V|³)。4.1 动态规划思想允许“中转”的路径优化Floyd算法的核心思想是动态规划。它考虑的问题是从顶点i到顶点j的最短路径如果允许使用前k个顶点编号1到k作为中转点这条最短路径的长度是多少我们定义一个三维状态dp[k][i][j]表示从i到j且只允许使用顶点1...k作为中间中转点的最短路径长度。但实际实现中我们可以用滚动数组优化到二维。状态转移方程是理解Floyd的钥匙 对于从i到j的路径当我们考虑是否允许使用第k个顶点作为中转时有两种选择不使用顶点k作为中转那么最短路径就是只允许使用前k-1个顶点时的最短路径即dp[k-1][i][j]。使用顶点k作为中转那么路径分解为i - k和k - j两段这两段路径都只允许使用前k-1个顶点。因此路径长度为dp[k-1][i][k] dp[k-1][k][j]。我们取两者中的最小值dp[k][i][j] min(dp[k-1][i][j], dp[k-1][i][k] dp[k-1][k][j])最终当k |V|时dp[|V|][i][j]就是允许使用所有顶点作为中转时从i到j的全局最短路径长度。4.2 算法流程与简洁实现Floyd算法的实现异常简洁通常直接在一个二维距离矩阵dist上进行“原地”更新。初始化时dist[i][j]就是邻接矩阵中边(i, j)的权值如果不存在直接边则为无穷大INFdist[i][i] 0。然后就是经典的三重循环def floyd_warshall(graph_matrix): Floyd算法计算所有顶点对之间的最短距离 :param graph_matrix: 邻接矩阵graph_matrix[i][j]表示i到j的直接距离INF表示无边 :return: 距离矩阵distdist[i][j]为i到j的最短距离 n len(graph_matrix) # 初始化距离矩阵为图的邻接矩阵的拷贝 dist [row[:] for row in graph_matrix] # 重要使用拷贝避免修改原矩阵 # 三重循环k在最外层 for k in range(n): for i in range(n): for j in range(n): # 如果通过k中转能使路径更短则更新 if dist[i][k] dist[k][j] dist[i][j]: dist[i][j] dist[i][k] dist[k][j] return dist # 示例使用之前的邻接矩阵这里我们将其视为有向图原矩阵是对称的 INF float(inf) graph [ [0, 10, 3, INF, INF], [INF, 0, 1, 2, INF], [INF, 4, 0, 8, 2], [INF, INF, INF, 0, 7], [INF, INF, INF, 9, 0] ] result floyd_warshall(graph) # 打印从A(0)到其他点的距离应与Dijkstra结果一致 print(从A出发:, result[0]) # 预期输出类似[0, 7, 3, 9, 5] # 打印所有点对之间的距离矩阵 for i in range(len(result)): print(f从顶点{i}出发: {result[i]})为什么k循环必须放在最外层这是Floyd算法正确性的关键。动态规划的状态dp[k][i][j]依赖于dp[k-1][...]。将k放在最外层保证了当我们计算以k为中转点时dist[i][k]和dist[k][j]存储的已经是只使用前k-1个顶点作为中转时的最短路径。如果打乱循环顺序就可能错误地使用了“已经允许以k为中转”的路径来更新其他路径导致结果错误。4.3 Floyd算法的适用场景与局限性Floyd算法以其实现简单、功能强大著称但它并非万能。优势代码极其简洁核心仅5行不易出错在建模快速原型阶段非常有用。一次性解决所有点对问题当问题确实需要计算所有顶点对之间的最短路径时Floyd的O(|V|³)可能比跑|V|次Dijkstra的O(|V| * (|V||E|) log |V|)更优尤其是在图非常稠密|E|接近|V|²时。可以处理负权边但不能有负权回路这是相比Dijkstra的一个优势。只要图中不存在从某点出发、经过一系列边后权值和为负的回路负权回路Floyd算法就能正确工作。负权回路会导致最短路径长度可以无限减小没有意义。劣势与注意事项时间复杂度高O(|V|³)意味着当顶点数超过1000时计算就可能非常缓慢。对于大规模稀疏图|V|次Dijkstra通常是更好的选择。空间复杂度需要O(|V|²)的矩阵存储所有点对距离对于顶点数巨大的图不友好。无法处理负权回路算法无法检测负权回路的存在。如果存在负权回路算法会陷入不断更新、距离无限减小的循环或者给出错误结果。在包含负权边的图中使用Floyd前需要先确保没有负权回路。路径记录上述代码只计算了最短距离。如果需要还原具体路径需要额外维护一个next矩阵next[i][j]表示从i到j的最短路径上i的下一个顶点是什么。在更新dist[i][j]时同步更新next[i][j] next[i][k]。5. 建模实战算法选择与问题变形在数学建模中直接套用标准算法往往不够。我们需要根据问题的具体约束和目标选择合适的算法并经常对其进行调整或组合。5.1 如何选择Dijkstra vs. Floyd vs. 其他面对一个最短路径问题如何决策特性Dijkstra算法Floyd算法考虑其他算法如Bellman-Ford, A*问题类型单源最短路径所有点对最短路径单源/特定点对权值要求必须非负可处理负权无负环Bellman-Ford可处理负权并检测负环时间复杂度O((|V||E|) log |V|)O(|V|³)Bellman-Ford: O(|V|*|E|)空间复杂度O(|V||E|) (邻接表)O(|V|²)通常O(|V||E|)适用场景稀疏图大规模网络单源查询稠密图小规模图需要所有点对结果有负权边或带有启发式信息的搜索A*选择策略如果只求一个起点到其他所有点的最短路径无脑选择Dijkstra优先队列版。这是最常见的情况。如果需要求所有点对之间的最短路径如果图非常稠密边数接近顶点数的平方且顶点数不大例如几百个可以考虑Floyd代码简单不易错。如果图是稀疏的或者顶点数很多跑 |V| 次Dijkstra通常更快。如果图中存在负权边绝对不能使用Dijkstra。可以选择Floyd前提是确认无负权回路或Bellman-Ford算法。Bellman-Ford还能检测图中是否存在从源点可达的负权回路。如果是在网格地图中寻找点对路径可以考虑A*搜索算法它通过启发式函数估计到终点的距离能显著减少搜索范围比Dijkstra更快。5.2 常见问题变形与建模技巧实际建模问题很少是标准的“求最短距离”。下面是一些常见变形及处理思路1. 求最短路径的条数问题在保证路径最短的前提下有多少条不同的最短路径 解法在运行Dijkstra或Floyd的同时维护一个计数数组count[]。以Dijkstra为例count[s]1。当发现一条新的、与当前最短距离相等的新路径时count[v] count[u]。当发现一条更短的路径时count[v] count[u]。2. 边权为多种成本多权值最短路径问题每条边有时间和金钱两种成本求在时间约束下的最小金钱路径或在金钱约束下的最短时间路径。 解法这是一个约束最短路径问题。一种方法是将其转化为状态空间搜索。定义状态(顶点, 已花费的约束资源)例如(u, time_used)。然后在扩展的状态图上运行Dijkstra算法边权是另一种资源如金钱。这本质上是分层图或动态规划的思想。3. 第K短路径问题不仅要求最短路径还要求第二短、第三短……第K短的路径。 解法使用Yen算法或Eppstein算法。一个简单但低效的方法是使用A*搜索的变种在搜索过程中不立即丢弃非最优路径而是维护一个优先队列保存当前找到的前K优路径。4. 动态图的最短路径问题边的权值如通行时间会随时间变化时变网络。 解法这非常复杂。一种近似方法是将时间离散化构建一个“时间-空间”状态网络然后在扩大的网络上运行最短路径算法。或者使用基于预测的实时算法。5. 必经点最短路径旅行商问题简化版问题要求路径从A出发经过B、C等指定点最后到达D。 解法如果必经点很少比如2-3个可以计算A、B、C、D两两之间的最短路径然后枚举经过这些点的顺序取总距离最小的排列。如果必经点较多就接近旅行商问题TSP是NP难的需要用启发式算法如遗传算法、模拟退火求解。建模心得在论文中描述算法时不要只写“我们使用了Dijkstra算法”。一定要说明为什么选择这个算法例如“由于本问题中所有道路长度为非负且仅需计算从配送中心到所有客户点的最短距离故采用效率较高的Dijkstra算法”以及如何将实际问题抽象成图定义顶点、边、权值。对于算法的任何修改或组合都需要清晰地阐述其动机和步骤。这能体现你对模型和算法的深入理解而非简单套用。6. 从理论到代码避坑指南与性能优化理解了原理最终还要落地到代码。这里分享一些在实现最短路径算法时容易出错和可以优化的点。6.1 常见错误与调试方法负权边导致Dijkstra结果错误这是最经典的错误。Dijkstra的贪心策略依赖于非负权值的假设。如果图中存在负权边一个当前看来距离远的点可能通过一条负权边变得很近导致算法提前将其标记为已确定从而得到错误结果。务必在算法开始前检查权值数据。无穷大INF设置不当在初始化距离和判断时INF需要设置为一个足够大的数但要避免溢出。通常用float(inf)Python或0x3f3f3f3fC/Java中一个常用的较大且相加不溢出的数。在更新距离时判断dist[u] w dist[v]如果dist[u]是INF加上w可能导致溢出变成负数从而错误地更新。安全的写法是if dist[u] ! INF and dist[u] w dist[v]。优先队列中的重复顶点在Dijkstra的优先队列实现中一个顶点可能因为距离被更新而多次入队。我们只关心第一次即距离最小出队的那次。因此if current_dist dist[u]: continue这行绝对不能省略它是保证效率的关键。Floyd算法中k循环顺序错误必须把中转点k的循环放在最外层。这是动态规划的阶段顺序错了就全错了。图的表示错误对于无向图邻接矩阵需要对称初始化邻接表则需要添加两条有向边。忘记这一点会导致路径查找失败。调试建议从小规模图开始手动演算将你的程序输出与手动计算结果对比。打印出算法每一步的关键变量如每轮松弛后的dist数组是定位错误的好方法。6.2 大规模图处理的性能优化当顶点数达到十万、百万级别时即使是O((|V||E|) log |V|)的Dijkstra也可能面临压力。以下是一些优化思路使用更高效的堆Python的heapq是二叉堆对于大规模数据使用Fibonacci Heap理论上可以将Dijkstra的时间复杂度降到O(|V| log |V| |E|)但常数较大。在实践中C的priority_queue或Java的PriorityQueue通常足够高效。双向搜索如果只关心从点A到点B的最短路径可以使用双向Dijkstra。同时从起点和终点开始运行Dijkstra当两个搜索的前沿相遇时路径即被找到。这能大幅减少搜索范围。启发式搜索A如果图具有地理信息如道路网络可以使用A算法。它为每个顶点定义一个启发式函数h(v)估计从v到目标点的距离。优先队列按照f(v) g(v) h(v)排序其中g(v)是起点到v的实际距离。一个好的启发式函数如直线距离能极大加速搜索。**A在找到最短路径的前提下访问的顶点数通常远少于Dijkstra*。预处理与分层对于静态图网络结构不变可以进行预处理。例如收缩层次算法通过识别和收缩不重要的顶点低度顶点生成一个层次化的、更小的图查询时在小图上进行。这需要额外的预处理时间和空间但能实现极快的查询速度常用于汽车导航系统。并行化Floyd算法的三重循环有很好的数据并行性可以用多线程或GPU加速。对于多源Dijkstra跑多次Dijkstra每次运行是独立的也可以并行处理。性能取舍经验在数学建模比赛中除非问题规模特别大否则优先实现正确、清晰的代码而不是追求极致的优化。清晰的代码更容易调试和解释。如果确实需要处理大规模数据在论文中应说明你采用的优化策略及其理论依据。例如“针对十万量级的路网节点我们采用了双向Dijkstra搜索将平均搜索空间降低了70%在标准测试集上将查询时间从XX毫秒降低到YY毫秒。”7. 综合案例城市应急物资配送路径规划让我们用一个简化的建模案例串联起从问题抽象、算法选择到求解分析的全过程。问题描述某市有一个应急物资中心顶点S需要在发生突发事件后将物资最快送达市内的7个重点安置点顶点A-G。已知城市道路网络无向图每条道路的通行时间已知。由于部分道路可能因灾情中断我们需要制定一个预案求出从中心S到每个安置点的最短通行时间路径并给出具体路线。同时为了冗余考虑还需要知道任意两个安置点之间的最短通行时间以备中心失效时安置点间相互支援。建模与求解步骤图模型构建顶点物资中心S以及7个安置点A-G。此外将道路网络的主要交叉口也抽象为顶点。假设共有N个顶点。边连接两个顶点的道路。由于道路可双向通行图为无向图。权值每条边的权值为车辆通过该道路所需的平均时间分钟。算法选择对于从S到所有安置点的最短路径这是一个单源最短路径问题。权值为时间非负因此选用Dijkstra算法。对于任意两个安置点之间的最短路径我们需要所有点对的最短路径。安置点只有7个但整个网络顶点N可能很大比如几百个。跑7次Dijkstra每次源点是一个安置点的时间复杂度是 O(7 * (NE) log N)。而使用Floyd算法的时间复杂度是 O(N³)。由于N可能远大于7且道路网络是稀疏图E ~ O(N)因此跑7次Dijkstra更高效。但如果我们需要所有顶点对包括交叉口之间的最短路径且N较小100则Floyd的代码简洁性更有优势。本例中我们选择前者。求解与结果分析运行Dijkstra算法源点为S。得到dist[S][v]和predecessor[v]前驱节点用于回溯路径。对于每个安置点通过回溯predecessor数组生成从S到该点的具体路径序列。以表格形式呈现结果安置点最短时间(分钟)最短路径序列A15S - J - K - AB22S - J - L - BC18S - M - C.........- 对于安置点对之间的最短时间我们以安置点A为源点运行一次Dijkstra得到A到B-G的距离再以B为源点...如此循环。最终可以生成一个7x7的对称矩阵称为“安置点间最短时间矩阵”。这个矩阵可以用于后续的设施选址、资源调配等分析。模型扩展讨论可靠性如果某些道路有失效概率可以将时间权值替换为“期望通行时间”或“最大可靠路径”。多物资中心如果有多个物资中心问题变为为每个安置点分配最近的中心即多源最短路径可以引入一个“超级源点”连接所有中心权值为0然后一次Dijkstra解决。动态权值如果通行时间随车流量变化则需要引入时变网络模型难度大增。通过这个案例我们可以看到最短路径算法不仅仅是求出几个数字更是连接问题抽象、模型构建、算法实现和结果分析的核心桥梁。掌握其原理和变通能为解决各类优化问题提供坚实的基础工具。
返回列表