恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

双关键字最短路径:Dijkstra与DFS解法及避坑指南

  • 首页
  • 资讯中心
  • /
  • 双关键字最短路径:Dijkstra与DFS解法及避坑指南

相关资讯

Java Map取key全攻略:keySet、entrySet、Stream与安全删除实战 2026/10/13 1:05:36
SAP CDS建模演进:从DEFINE VIEW到View Entity迁移实战 2026/10/13 1:05:36
claude-mem:为Claude Code打造自动跨会话记忆层 2026/10/13 1:00:36

最新资讯

Learning Processing 系列课程主题全景导读:从像素绘制到面向对象编程
5 分钟装好 REA 命令:curl 一行脚本与 npm 全局安装完整指南
aiogram 编辑 Checklist 消息:EditMessageChecklist 方法深度解析与实践指南
从源码构建 Deepfence ThreatMapper:本地编译容器镜像、启动管理控制台与镜像发布全指南
Apache OpenDAL Haskell Binding 实战:用 Haskell 统一访问 50+ 存储服务
Bresenham画圆算法:整数微分与八分对称的嵌入式图形实现

今日推荐

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析
CAN总线错误处理机制深度解析:从位错误到Bus Off的硬件级调试

本周热门

UE动画修改实战:从资产编辑到重定向与蒙太奇驱动
统计随机数生成器攻击下的KLJN安全密钥交换协议Matlab仿真
政务API安全治理:资产测绘、低代码编排与行标对标实践

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

双关键字最短路径:Dijkstra与DFS解法及避坑指南

发布时间:2026/10/13 1:05:36
双关键字最短路径:Dijkstra与DFS解法及避坑指南 1. 从一道题看最短路径的两种解法旅游规划这道题表面上是让你帮人规划一条从出发地到目的地的最优路线实际上它是一道非常经典的单源最短路径应用题。题目通常给出若干城市、若干条道路每条道路有长度和过路费两个权重要求你在保证路径总长度最短的前提下选择花费最少的那条路如果长度相同、花费也相同则输出任意一条即可。这类题在算法训练中出现的频率极高因为它同时考察了两个核心能力图论建模和双关键字最短路处理。我第一次做这道题的时候直接套了一个朴素的Dijkstra模板上去结果只过了部分测试点。后来才发现问题出在“距离相同但花费不同”这个细节上——普通的Dijkstra只维护一个距离数组遇到相等距离时不会更新花费导致最终输出的花费不是最优的。这个坑非常典型很多刚接触最短路的同学都会踩。这篇文章我会从零开始把这道题的完整思路、两种实现方式DFS和Dijkstra、双关键字的处理技巧、以及实际编码中容易翻车的地方全部讲清楚。不管你是刚学图论的新手还是想复习最短路的老手都能从中找到有用的东西。尤其是那些“模板能过但一遇到变体就挂”的朋友这篇内容应该能帮你打通一些关节。2. 题目核心需求拆解与建模思路2.1 题目到底在问什么先把题目翻译成人话。假设有N个城市编号从0到N-1城市之间有M条双向道路。每条道路有两个属性长度distance和过路费cost。现在给定起点S和终点D你需要找一条从S到D的路径满足以下优先级路径的总长度最小在总长度最小的所有路径中总花费最小输出最小花费有些版本要求输出路径本身但大多数版本只要求输出两个数值。这就是典型的双权最短路径问题。注意这里的两个权重不是简单的相加关系而是有优先级的——先比距离距离相同再比花费。这一点非常关键决定了你不能简单地把两个权重合并成一个值来做。2.2 为什么不能简单合并权重有些同学会想那我直接把距离和花费加权求和不就行了比如new_weight distance * 1000 cost然后跑普通Dijkstra。这个思路在某些情况下确实能work但前提是你得保证距离的权重远大于花费而且花费不会溢出。问题是如果距离的范围很大比如达到10^9你乘1000之后可能就溢出了如果花费的范围也很大你没法保证优先级严格正确。更稳妥的做法是在Dijkstra的松弛过程中同时维护距离和花费两个数组当距离相等时额外判断花费。这样既不会溢出也能严格保证优先级。2.3 图的存储方式选择对于这道题N和M的范围通常不会特别大比如N≤500M≤N*(N-1)/2所以用邻接矩阵或者邻接表都可以。邻接矩阵的优点是写起来简单松弛的时候直接遍历所有点缺点是空间复杂度O(N^2)如果N到了10^5就炸了。邻接表的优点是空间效率高适合稀疏图缺点是需要手写链表或者用vector代码稍微长一点。我的建议是如果题目没有明确给出N的范围优先用邻接表vector实现因为它的适用性更广。如果N很小比如≤1000邻接矩阵写起来更快不容易出错。提示这道题的道路是双向的所以加边的时候要加两次。很多同学只加了一次结果样例都过不了。3. Dijkstra解法的完整实现与细节剖析3.1 Dijkstra的核心思想回顾Dijkstra算法的本质是贪心广度优先搜索。它维护一个距离数组dist[]初始时起点的距离为0其他点为无穷大。然后每次从尚未访问的点中选出距离最小的那个标记为已访问并用它去松弛它的所有邻居。重复这个过程直到所有点都被访问或者终点被访问。这个算法的正确性依赖于一个前提所有边的权重非负。如果存在负权边Dijkstra就会失效需要用Bellman-Ford或者SPFA。这道题的道路长度和花费显然都是非负的所以Dijkstra完全适用。3.2 双关键字的具体处理方式普通Dijkstra只维护一个dist[]数组松弛条件是dist[v] dist[u] w。现在我们需要维护两个数组dist[]和cost[]。松弛逻辑变成如果dist[v] dist[u] d说明找到了一条更短的路更新dist[v]和cost[v]如果dist[v] dist[u] d且cost[v] cost[u] c说明距离相同但花费更少更新cost[v]其他情况不更新。这个逻辑看起来简单但有一个容易忽略的点当距离相等且花费也相等时不需要做任何操作。有些同学会写成导致无限循环或者重复更新虽然不会影响最终结果但会浪费 time。3.3 完整代码实现邻接表版下面是我常用的邻接表Dijkstra模板直接可以套用到这道题上#include bits/stdc.h using namespace std; const int INF 0x3f3f3f3f; const int MAXN 505; struct Edge { int to, dist, cost; Edge(int t, int d, int c) : to(t), dist(d), cost(c) {} }; vectorEdge graph[MAXN]; int dist[MAXN], cost[MAXN]; bool visited[MAXN]; int n, m, s, d; void dijkstra(int start) { memset(dist, 0x3f, sizeof(dist)); memset(cost, 0x3f, sizeof(cost)); memset(visited, false, sizeof(visited)); dist[start] 0; cost[start] 0; for (int i 0; i n; i) { int u -1, minDist INF; for (int j 0; j n; j) { if (!visited[j] dist[j] minDist) { minDist dist[j]; u j; } } if (u -1) break; visited[u] true; for (auto e : graph[u]) { int v e.to; if (visited[v]) continue; if (dist[v] dist[u] e.dist) { dist[v] dist[u] e.dist; cost[v] cost[u] e.cost; } else if (dist[v] dist[u] e.dist) { if (cost[v] cost[u] e.cost) { cost[v] cost[u] e.cost; } } } } } int main() { cin n m s d; for (int i 0; i m; i) { int u, v, dd, cc; cin u v dd cc; graph[u].push_back(Edge(v, dd, cc)); graph[v].push_back(Edge(u, dd, cc)); } dijkstra(s); cout dist[d] cost[d] endl; return 0; }这段代码的核心就是那个if-else if结构。注意第二个分支里我用了而不是这是为了避免不必要的更新。另外visited[v]的判断也很重要——如果v已经被访问过了说明它的最短距离已经确定不需要再松弛了。3.4 时间复杂度分析朴素Dijkstra的时间复杂度是O(N^2)因为每次选最小点需要遍历所有点一共选N次。如果N500N^2250000完全可以在1秒内跑完。如果N更大比如10^5就需要用优先队列优化到O(M log N)。不过对于这道题来说朴素版本足够了。注意如果你用优先队列优化记得在弹出元素时判断if (d dist[u]) continue;否则会重复处理同一个点导致超时。4. DFS解法的适用场景与实现要点4.1 DFS为什么也能做这道题DFS深度优先搜索本质上是一种暴力枚举它会尝试从起点到终点的所有可能路径然后从中选出最优的那条。对于小规模的图比如N≤30DFS完全可行而且写起来比Dijkstra更直观——你只需要在递归的过程中维护当前的距离和花费到达终点时更新全局最优解即可。但是DFS的致命缺点是时间复杂度指数级。如果图比较稠密路径数量会爆炸程序直接跑死。所以DFS只适合N很小的情况或者作为对拍验证Dijkstra正确性的工具。4.2 DFS的剪枝优化如果你非要用DFS有几个剪枝技巧可以大幅提升效率最优性剪枝如果当前距离已经大于已知的最优距离直接返回如果当前距离等于最优距离但花费已经大于已知最优花费也直接返回。访问标记用一个visited[]数组标记当前路径上已经访问过的点避免走回头路。注意回溯时要取消标记。邻接表排序把邻居按照距离从小到大排序这样更有可能先找到较优的解从而让剪枝更早生效。下面是一个带剪枝的DFS实现#include bits/stdc.h using namespace std; const int INF 0x3f3f3f3f; const int MAXN 505; struct Edge { int to, dist, cost; }; vectorEdge graph[MAXN]; bool visited[MAXN]; int bestDist INF, bestCost INF; int n, m, s, d; void dfs(int u, int curDist, int curCost) { if (u d) { if (curDist bestDist || (curDist bestDist curCost bestCost)) { bestDist curDist; bestCost curCost; } return; } if (curDist bestDist) return; if (curDist bestDist curCost bestCost) return; for (auto e : graph[u]) { if (visited[e.to]) continue; visited[e.to] true; dfs(e.to, curDist e.dist, curCost e.cost); visited[e.to] false; } } int main() { cin n m s d; for (int i 0; i m; i) { int u, v, dd, cc; cin u v dd cc; graph[u].push_back({v, dd, cc}); graph[v].push_back({u, dd, cc}); } visited[s] true; dfs(s, 0, 0); cout bestDist bestCost endl; return 0; }4.3 DFS vs Dijkstra怎么选对比维度DFSDijkstra时间复杂度O(2^N) 最坏O(N^2) 或 O(M log N)适用规模N ≤ 30N ≤ 10^5代码难度简单直观中等是否支持负权支持不支持双关键字处理自然支持需要额外判断我的建议是正式比赛或者作业提交一律用Dijkstra。DFS只用来对拍或者理解题意。如果你时间充裕可以两种都写一遍对比一下结果是否一致这样能加深对最短路问题的理解。5. 常见错误与排查技巧实录5.1 为什么我的Dijkstra只过了部分测试点这是最常见的问题。原因通常有以下几种忘记处理距离相等的情况只写了dist[v] dist[u] d的更新没有写dist[v] dist[u] d时更新花费的逻辑。这是最典型的错误。花费数组没有初始化cost[]数组忘记初始化为INF导致比较时出现随机值。双向边只加了一次题目说的是双向道路你只加了u-v没加v-u导致某些路径走不通。起点和终点相同如果sd应该直接输出0 0但有些代码会陷入死循环或者输出错误值。5.2 优先队列优化版的坑如果你用优先队列写Dijkstra有一个非常隐蔽的坑优先队列里存的是pair默认按照first排序。如果你把距离放在first花费放在second那么当距离相等时队列会按照花费排序这本身没问题。但如果你把花费放在first距离放在second就会导致距离不是最小的先被弹出算法直接失效。正确的做法是priority_queuepairint,int, vectorpairint,int, greaterpairint,int pq;然后pq.push({dist, node})。这样距离小的优先弹出距离相等时节点编号小的优先弹出但这不影响正确性。5.3 常见问题速查表问题现象可能原因解决方法样例过了但提交WA距离相等时未更新花费加上else if分支程序运行超时用了DFS且N较大换成Dijkstra输出花费为INF终点不可达检查边是否双向加结果比正确答案大花费数组未初始化memset为0x3f起点终点相同输出错误未特判加if(sd)判断5.4 一个容易被忽略的细节有些版本的题目要求输出的是路径而不是花费。这时候你需要在Dijkstra的过程中额外维护一个pre[]数组记录每个点的前驱节点。当距离更新时同时更新前驱当距离相等且花费更小时也要更新前驱。最后从终点倒推回起点再反转输出即可。这个细节看起来简单但实际操作时很容易忘记在第二个分支里更新pre[]导致输出的路径不是花费最小的那条。6. 从这道题延伸出的通用解题框架6.1 双关键字最短路的通用模板把这道题的思路抽象一下可以得到一个处理多关键字最短路的通用框架确定关键字的优先级顺序比如距离 花费 时间为主关键字维护dist[]数组为次关键字维护cost[]数组在松弛时先比较主关键字再比较次关键字如果还有第三关键字继续嵌套判断。这个框架可以扩展到任意数量的关键字只要你能明确优先级顺序。6.2 什么时候该用Dijkstra什么时候该用其他算法边权非负、单源最短路Dijkstra边权有负、单源最短路Bellman-Ford或SPFA多源最短路Floyd-WarshallN≤500或对每个源跑一次Dijkstra带约束的最短路比如“最多经过K条边”需要用动态规划或者分层图。这道题属于第一种情况所以Dijkstra是最优解。6.3 实际工程中的应用场景最短路径算法不仅仅是算法题它在实际工程中有着广泛的应用地图导航计算从A地到B地的最短行车路线同时考虑距离和拥堵时间网络路由数据包在网络中传输时选择延迟最小的路径物流配送在保证时效的前提下选择成本最低的配送路线游戏AINPC寻路时选择最短路径同时避开危险区域。这些场景的共同点是多个权重需要同时考虑且有优先级。掌握了双关键字最短路你就有了解决这类问题的基础能力。6.4 进一步练习的建议如果你想巩固这道题的知识点可以尝试以下变体把距离和花费的优先级反过来先比花费再比距离增加第三个关键字比如时间要求输出完整路径而不是数值把双向边改成单向边增加“必须经过某个点”的约束。每一个变体都会让你对最短路问题有更深的理解。我自己当年就是把这道题反复改了五六遍才真正把Dijkstra的每一个细节都吃透。最后分享一个我个人的习惯每次写完Dijkstra我都会用DFS写一个暴力版本然后随机生成小规模数据对拍。这个方法帮我抓出了无数个隐蔽的bug强烈推荐你也试试。

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号