基础图论 - 最短路

基础图论之最短路

 

朴素版的Dijkstra算法

AcWing - Dijkstra求最短路 I

  • 稠密图:数据范围 m~n^2  (m = 1e5, n = 500),复杂度n^2,邻接矩阵存图
  • 解题思路:外层迭代 n 次,每次确定一个点的最短路,st[i] 表明当前已确定最短距离的点,确定最短距离的点 t 之后,用 t 更新其他点的距离。
#include <iostream> #include <cstring> using namespace std; const int N = 510; int n, m; int g[N][N]; //稠密图用邻接矩阵存,稀疏图用邻接表存 int dist[N]; //表示从1号点到该点的当前的最短路 bool st[N]; //判断该点的最短路是否已经确定 int dijkstra() { memset(dist, 0x3f, sizeof dist); dist[1] = 0; for(int i = 0; i < n; i ++) //迭代n次,每次确定一个点的最短路 { int t = -1; for(int j = 1; j <= n; j ++) if(!st[j] && (t == -1 || dist[t] > dist[j])) //如果当前的t不是最短的,更新为j t = j; st[t] = true; //表明t已经完成最短路径 for(int j = 1; j <= n; j ++) dist[j] = min(dist[j], dist[t] + g[t][j]); //用t更新到点j的最短路径,即一号点到t的距离+t到j的距离 } if(dist[n] == 0x3f3f3f3f) return -1; return dist[n]; } int main() { cin >> n >> m; memset(g, 0x3f, sizeof g); while(m --) { int a, b, c; cin >> a >> b >> c; g[a][b] = min(g[a][b], c); //该题会有重边,只需取边权最小的一条边 } int t = dijkstra(); cout << t << endl; return 0; }

 

堆优化版的Dijkstra算法

AcWing - Dijkstra求最短路 II

  • 稀疏图:数据范围 m~n  (m = 1e5, n = 1e5),复杂度mlogn,邻接表存图
  • 解题思路:遍历每条边,如果dist[j] > distance + w[i], 则更新到点 j 的距离,队列维护点 j 以及从源点到点 j 的最短距离,遍历边的时间复杂度 m ,在优先队列中更新一个点的时间复杂度为 logn,总时间复杂度为 mlogn,st[i]含义同上。
#include <iostream> #include <algorithm> #include <queue> #include <cstring> using namespace std; const int N = 1e6+10; typedef pair<int, int> PII; int n, m; int h[N], e[N], ne[N], w[N], idx; //w[N]记录权重 int dist[N]; bool st[N]; void add(int a, int b, int c) { e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++; } int dijkstra() { memset(dist, 0x3f, sizeof dist); dist[1] = 0; priority_queue<PII, vector<PII>, greater<PII> >heap; heap.push({0, 1}); //前一个数表示距离,后一个数表示编号,此时将编号唯一的点加入队列 while(heap.size()) { auto t = heap.top(); heap.pop(); int distance = t.first, ver = t.second; if(st[ver]) continue; st[ver] = true; for(int i = h[ver]; i != -1; i = ne[i]) { int j = e[i]; if(dist[j] > distance + w[i]) { dist[j] = distance + w[i]; heap.push({dist[j], j}); } } } if(dist[n] == 0x3f3f3f3f) return -1; return dist[n]; } int main() { scanf("%d%d", &n, &m); memset(h, -1, sizeof h); while(m --) { int a, b, c; scanf("%d%d%d", &a, &b, &c); add(a, b, c); } int t = dijkstra(); cout << t; return 0; }

 

Bellman_ford算法

AcWing - 有边数限制的最短路

  • 时间复杂度:O(mn) ,外层 k(k~n)个循环,确定从1号点到n号点的最多经过k条边的最短距离,内从每次遍历m条边。
  • Bellman_ford算法着重解决有边数限制的最短路。
#include <iostream> #include <cstring> using namespace std; const int N = 510, M = 100010; int n, m, k; int dist[N], backup[N]; struct Edge { int a, b, w; }edge[M]; int Bellman_ford() { memset(dist, 0x3f, sizeof dist); dist[1] = 0; for(int i = 1; i <= k; i ++) //遍历k次,从1号点到n号点的最多经过k条边的最短距离 { memcpy(backup, dist, sizeof backup); //跟前几个不同的是,该题有边数限制,不能时时用前一个点的距离去更新后一个点的距离 for(int j = 1; j <= m; j ++) { int a = edge[j].a, b = edge[j].b, w = edge[j].w; dist[b] = min(dist[b], backup[a] + w); //例:当j为1时,此时dist[2]=1,求dist[3]时,不能用dist[2]去更新到点3的距离,因为题中要求只经过一条边,此时只能用dist[1]去更新距离, //也即backup[1]+w,假如题中要求两条边,则会重新将dist[2]的值赋值给backup[2],此时再求dist[3]是就可用dist[2]更新,即backup[2]+w } } if(dist[n] > 0x3f3f3f3f / 2) cout << "impossible"; else cout << dist[n]; } int main() { cin >> n >> m >> k; for(int i = 1; i <= m; i ++) { int a, b, w; cin >> a >> b >> w; edge[i] = {a, b, w}; } Bellman_ford(); return 0; }

 

SPFA算法

AcWing - spfa求最短路

AcWing - spfa判断负环

  • SPFA算法 是对 Bellman_ford 算法的优化,后者会遍历所有的边,因此会有很多无意义的更新,而我们只用遍历那些到源点距离变小的点所连接的边即可,只有当一个点的前驱结点更新,该节点才会得到更新,因此考虑到这一点,我们将创建一个队列每一次加入距离被更新的结点。
  • spfa可以判断负环,因为只要距离更新就会有元素入队,可以用一个cnt数组记录每个点到源点的边数,一个点被更新一次就+1,一旦有点的边数达到了n,就证明存在负环。
#include <iostream> #include <cstring> #include <queue> using namespace std; const int N = 1e5 + 10; int n, m; int w[N], ne[N], h[N], e[N], idx; bool st[N]; int dist[N]; void add(int a, int b, int c) { e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++; } void spfa() { memset(dist, 0x3f, sizeof dist); dist[1] = 0; queue<int> q; q.push(1); st[1] = true; while(q.size()) { int t = q.front(); q.pop(); st[t] = false; //从队列中取出来之后该节点st被标记为false,代表之后该节点如果发生更新可再次入队 for(int i = h[t]; i != -1; i = ne[i]) { int j = e[i]; if(dist[j] > dist[t] + w[i]) { dist[j] = dist[t] + w[i]; //当前已经加入队列的结点,无需再次加入队列,即便发生了更新也只用更新数值即可,重复添加降低效率 if(!st[j]) { q.push(j); st[j] = true; } } } } if(dist[n] == 0x3f3f3f3f) cout << "impossible"; else cout << dist[n]; } int main() { cin >> n >> m; memset(h, -1, sizeof h); while(m --) { int a, b, c; cin >> a >> b >> c; add(a, b, c); } spfa(); return 0; }

 

Floyd算法

AcWing - Floyd求最短路

  • Floyd 算法着重解决多源最短距离,时间复杂度 O(n ^ 3)
#include<iostream> using namespace std; const int N = 210, INF = 1e9; int n, m, q; int d[N][N]; void floyd() { //从 i 到 j 经过点 k 的距离 for(int k = 1; k <= n; k ++) for(int i = 1; i <= n; i ++) for(int j = 1; j <= n; j ++) d[i][j] = min(d[i][j], d[i][k] + d[k][j]); //从 i 到 k 的距离 + 从 k 到 j 的距离 //f[i, j, k] = min(f[i, j, k - 1], f[i, k, k - 1] + f[k, j, k - 1]) } int main() { cin >> n >> m >> q; for(int i = 1; i <= n; i ++) { for(int j = 1; j <= n; j ++) { if(i == j) d[i][j] = 0; //解决自环 else d[i][j] = INF; } } while(m --) { int a, b, c; cin >> a >> b >> c; d[a][b] = min(d[a][b], c); } floyd(); while(q --) { int a, b; cin >> a >> b; int t = d[a][b]; if(d[a][b] > INF / 2) cout << "impossible" << endl; else cout << t << endl; } return 0; }

 

注意事项和区别

https://www.acwing.com/file_system/file/content/whole/index/content/369161/

为什么Dijkstra不能使用在含负权的图中?

https://www.acwing.com/solution/content/6320/


__EOF__

本文作者breeze_ku
本文链接https://www.cnblogs.com/breeze-ku/p/17087802.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   breeze_ku  阅读(45)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App
点击右上角即可分享
微信分享提示