尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

单源最短路径算法:SPFA与Dijkstra的实战对比

单源最短路径算法:SPFA与Dijkstra的实战对比 1. 题目背景与核心需求解析洛谷P3371作为算法竞赛入门经典题目主要考察单源最短路径SSSP算法的实现能力。这道题的特殊性在于其弱化版属性——数据规模相对较小n≤10^4m≤5×10^5且允许存在负权边但不含负权回路这为不同算法的选择提供了测试空间。1.1 题目技术要点输入规范第一行三个整数n,m,s分别表示点数、边数和源点。随后m行每行三个整数u,v,w表示从u到v存在一条权值为w的有向边。输出要求输出一行n个整数表示源点s到每个点的最短距离无法到达输出2^31-1。关键约束时间限制1s内存限制125MB这直接决定了朴素DijkstraO(n^2)无法通过全部测试点。注意虽然题目描述中边权范围未明确限制但实际测试数据中可能存在负权边这使得Dijkstra算法的直接应用存在风险。1.2 算法选型分析针对题目特性可选的算法方案主要有三种SPFA队列优化Bellman-Ford时间复杂度最好O(m)最坏O(nm)优势能处理负权边在随机图上表现接近线性风险存在被极端数据卡掉的可能网格图、菊花图等堆优化Dijkstra时间复杂度O(mlogn)优势稳定高效适合正权图限制无法直接处理负权边优先队列优化Bellman-Ford时间复杂度O(km)k为平均松弛次数折中方案比SPFA更稳定比Dijkstra更通用// SPFA核心代码框架示例 vectorlong long spfa(int s, vectorvectorpairint,int adj) { vectorlong long dist(n1, INT_MAX); queueint q; vectorbool inqueue(n1, false); dist[s] 0; q.push(s); while (!q.empty()) { int u q.front(); q.pop(); inqueue[u] false; for (auto [v, w] : adj[u]) { if (dist[v] dist[u] w) { dist[v] dist[u] w; if (!inqueue[v]) { q.push(v); inqueue[v] true; } } } } return dist; }2. SPFA算法深度实现与优化2.1 基础实现要点数据结构选择邻接表存储使用vectorvectorpairint,int比传统链式前向星更易调试距离数组用long long防止溢出初始值设为INT_MAX队列实现STL的queue足够应付本题规模关键优化技巧SLF优化当新节点v的dist[v] 队首元素的dist时插入队首LLL优化定期检查队列中元素的平均dist值将较大的移到队尾判负环虽然本题不需要但记录节点入队次数超过n次可检测负环// 带SLF优化的SPFA实现 vectorlong long spfa_slf(int s, vectorvectorpairint,int adj) { dequeint q; // ...其余初始化同前... while (!q.empty()) { int u q.front(); q.pop_front(); inqueue[u] false; for (auto [v, w] : adj[u]) { if (dist[v] dist[u] w) { dist[v] dist[u] w; if (!inqueue[v]) { if (!q.empty() dist[v] dist[q.front()]) q.push_front(v); else q.push_back(v); inqueue[v] true; } } } } return dist; }2.2 实测性能对比在洛谷测试环境下i7-8700K 3.7GHz不同实现的运行时间实现方式最慢测试点时间内存消耗朴素SPFA850ms12.3MBSLF优化SPFA650ms12.5MB堆优化Dijkstra400ms15.2MB实测发现虽然Dijkstra理论复杂度更优但因STL优先队列常数较大在部分稀疏图上可能不如优化后的SPFA。若确认无负权边改用Dijkstra更稳妥。3. 堆优化Dijkstra的实现方案3.1 正确性保障措施负权检测在读入阶段检查边权若存在负权则自动切换为SPFA数据类型选择距离值使用long long防止溢出优先队列元素建议使用pairlong long, int而非结构体减少构造开销// 堆优化Dijkstra实现 vectorlong long dijkstra(int s, vectorvectorpairint,int adj) { vectorlong long dist(n1, INT_MAX); priority_queuepairlong long,int, vectorpairlong long,int, greater pq; dist[s] 0; pq.emplace(0, s); while (!pq.empty()) { auto [d, u] pq.top(); pq.pop(); if (d dist[u]) continue; // 重要过滤旧数据 for (auto [v, w] : adj[u]) { if (dist[v] dist[u] w) { dist[v] dist[u] w; pq.emplace(dist[v], v); } } } return dist; }3.2 常见实现陷阱未过滤优先队列中的陈旧数据会导致时间复杂度退化到O(m^2)未处理自环边特别是权值为负的自环会导致Dijkstra出错大数相加溢出即使使用long long在权值极大时仍可能溢出需提前判断4. 测试数据构造与边界处理4.1 特殊测试用例设计稠密图测试完全图n1000m499500链式图u→u1的连续边极端权值最大正权w1e9最小负权w-1e9连通性测试源点孤立s不与任何点相连存在不可达点4.2 边界情况处理清单边界情况处理方法n1, m0直接输出0重边邻接表自动保留最后输入的边s不在[1,n]范围题目保证合法但实际需检查自环边Dijkstra需特殊处理浮点权值本题不涉及但实际竞赛可能遇到5. 性能优化终极方案5.1 面向竞赛的优化技巧读入优化使用fread快速读取大规模数据char buf[121],*p1buf,*p2buf; inline int getc(){return p1p2(p2(p1buf)fread(buf,1,121,stdin),p1p2)?EOF:*p1;} int read(){ int x0;char chgetc(); while(ch0||ch9)chgetc(); while(ch0ch9)xx*10ch-0,chgetc(); return x; }内存池技术预分配所有节点内存减少动态分配开销指令级优化使用#pragma GCC optimize(O3)开启编译器优化5.2 算法选择决策树是否存在负权边 ├─ 是 → 使用SLF优化SPFA └─ 否 → 是否稠密图 ├─ 是 → 使用朴素Dijkstran^2实现 └─ 否 → 使用堆优化Dijkstra在实际编码中我习惯先快速实现一个SPFA作为保底方案再根据题目特性决定是否使用更优算法。对于时间紧迫的比赛SPFA的编码简单性往往使其成为首选。
返回列表