最短路
最短路算法
最短路的分类
根据不同的需求选择不同的最短路算法
图论的算法考试的侧重点在建图和算法实现
所以证明并不是那么重要
朴素版dijkstra算法
基于贪心
算法实现:
#include<iostream> #include<cstring> #include<algorithm> using namespace std; const int N = 510; int n,m; int g[N][N],dist[N]; bool st[N]; int dijkstra(){ memset(dist,0x3f,sizeof dist); dist[1]=0; for(int i=0;i<n;i++){ int t=-1; for(int j=1;j<=n;j++) if(!st[j]&&(t==-1||dist[t]>dist[j])) t=j; st[t]=true; for(int j=1;j<=n;j++) dist[j]=min(dist[j],dist[t]+g[t][j]); } if(dist[n]==0x3f3f3f3f) return -1; return dist[n]; } int main(){ scanf("%d%d",&n,&m); memset(g,0x3f,sizeof g); while(m--){ int a,b,c; scanf("%d%d%d",&a,&b,&c); g[a][b]=min(g[a][b],c); } int t=dijkstra(); printf("%d\n",t); return 0; }
堆优化版dijkstra算法
在朴素版dijkstra中寻找距离当前点最近没确定最短距离的点的时候要循环n次
我们可以维护一个小根堆来查询距离最近的没确定最短距离的点
这样就可以将这一步转化为O(1)
如果时手写堆,支持删除任意元素,那么堆里面总共的元素个数只有n个
还可以用stl中的priority_queue,但这样堆中会有m个元素
这样原本的mlogn就会变为mlogm
但因为m<=n2,所以logm<=logn2,又因为logn^2=2logn,所以logm<=2logn
所以mlogm可以写成mlogn
算法实现:
#include<iostream> #include<cstring> #include<algorithm> #include<queue> using namespace std; typedef pair<int,int> PII; const int N=100010; int n,m; int dist[N]; // 堆优化版dijkstra由于稀疏图 // 所以要用邻接表来存 int h[N],e[N],ne[N],w[N],idx; bool st[N]; void add(int a,int b,int c){ e[idx]=b,ne[idx]=h[a],w[idx]=c,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; else 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(); printf("%d\n",t); return 0; }
Bellman-Ford算法
Bellman-Ford算法十分简单
时间复杂度:两层循环,O(nm)
#include <iostream> #include <cstring> using namespace std; const int N = 510, M = 10010; int n, m, k; int dist[N], backup[N]; struct Edge { int a, b, w; }edges[M]; int bellman_ford() { memset(dist, 0x3f, sizeof dist); dist[1] = 0; for (int i = 0; i < k; i++) { memcpy(backup, dist, sizeof dist); for (int j = 0; j < m; j++) { int a = edges[j].a, b = edges[j].b, w = edges[j].w; dist[b] = min(dist[b], backup[a] + w); } } if (dist[n] > 0x3f3f3f3f / 2) return -1; return dist[n]; } int main() { scanf("%d%d%d", &n, &m, &k); for (int i = 0; i < n; i++) { int a, b, w; scanf("%d%d%d", &a, &b, &w); edges[i] = {a, b, w}; } int t = bellman_ford(); if (t == -1) printf("impossible\n"); else printf("%d\n", t); return 0; }
为什么bellman-ford算法模板中最后要这么判断
因为如果有两个点都是正无穷,但是他俩之间是一条负边,就有可能会更新
spfa算法
spfa算法是在Bellman-Ford算法上做了一个优化
Bellman-Ford算法每延申一步都要遍历所有边
但并不是每一个都会更新
只有上一个点更新,这个点才有可能更新
所以可以用一个队列来存更新过的点
每次取出一个对头,用它来更新它可更新的点
算法实现:
#include <iostream> #include <queue> #include <cstring> using namespace std; const int N = 100010; int n, m; int dist[N]; int h[N], e[N], ne[N], w[N], idx; bool st[N]; void add(int a, int b, int c) { e[idx] = b; ne[idx] = h[a]; w[idx] = c; h[a] = idx ++ ; } int 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; 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) 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 = spfa(); if (t == -1) printf("impossible\n"); else printf("%d\n", t); return 0; }
spfa判负环
跑一遍spfa最短路记一个每一个点距离起点走过了多少边
算法实现:
#include <iostream> #include <cstring> #include <queue> using namespace std; const int N = 100010; int n, m; int dist[N], cnt[N]; int h[N], e[N], ne[N], w[N], idx; bool st[N]; void add(int a, int b, int c) { e[idx] = b; ne[idx] = h[a]; w[idx] = c; h[a] = idx ++ ; } bool spfa() { memset(dist, 0x3f, sizeof dist); queue<int> q; for (int i = 1; i <= n; i ++ ) q.push(i), st[i] = true; while (q.size()) { int t = q.front(); q.pop(); st[t] = 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]; cnt[j] = cnt[t] + 1; if (cnt[j] >= n) return true; if (!st[j]) { q.push(j); st[j] = true; } } } } return false; } 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=spfa(); if (t) printf("Yes\n"); else printf("No\n"); return 0; }
Floyd算法
Floyd算法基于动态简单的动态规划
状态转换方程:dist[i, j] = min(dist[i, j], dist[i, k] + dist[k, j])
算法实现:
#include <iostream> using namespace std; const int N = 210, INF = 1e9; int n, m, Q; int d[N][N]; void floyd() { 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]); } int main() { scanf("%d%d%d", &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, w; scanf("%d%d%d", &a, &b, &w); d[a][b] = min(d[a][b], w); } floyd(); while (Q -- ) { int a, b; scanf("%d%d", &a, &b); if (d[a][b] > INF / 2) printf("impossible\n"); else printf("%d\n", d[a][b]); } return 0; }
本文作者:张詠然
本文链接:https://www.cnblogs.com/zyrddd/p/16634391.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析