最短路径
-
朴素dijkstra
-
思路:
第一步先初始化dist[1] = 0,dist[i] = INF (dist数组表示某一点到起点的距离);
第二步第一层循环for循环 1 - n遍历所有点,然后第二层循环for 1 - n,找到 st[t] 值为 false 且距离起点最近的点的下标值赋给变量 t 并且把st[t]的值设为true( st 数组是标记这个点的最短路径是否已经找到,找到则为true);
第三步用 t 更新其它所有点的距离。
-
例题:
-
代码实现:
#include <bits/stdc++.h> using namespace std; const int N = 510; int n,m; int g[N][N]; //存储每一条边的距离(邻接矩阵适合稠密图) int dist[N]; //存储每一个点到起点的距离 bool st[N]; // 标记某一点的最短路径是否已经确定下来 int dijkstra() { memset(dist, 0x3f, sizeof dist); dist[1] = 0; for(int i = 1; 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); for(int i = 1; i <= m; i ++ ) { int a,b,w; scanf("%d%d%d", &a, &b, &w); g[a][b] = min(g[a][b], w); } int res = dijkstra(); printf("%d",res); return 0; }
-
-
堆优化版dijkstra:
-
优化了找最小值所需的时间;
-
例题:
-
代码实现:
#include <bits/stdc++.h> using namespace std; typedef pair<int,int> PII;//<距离起点的距离,节点编号> const int N = 150010; int n,m,idx;//当前指到哪个邻接点 int h[N],w[N],e[N],ne[N];//e数组是存储 int dist[N]; bool st[N]; //在a节点之后插入一个b节点,权重为c 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;//1号节点距离起点为0 priority_queue<PII, vector<PII>, greater<PII>> heap;//定义小根堆 heap.push({0,1});//将1号节点插入堆 while(heap.size()) { auto t = heap.top();//取出堆顶顶点,因为是小根堆,所以堆顶就是最小值 heap.pop(); int ver = t.second, distance = t.first; 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 res = dijkstra(); printf("%d\n",res); return 0; }
-
-
Bellman_ford:
-
思路:
图用结构体实现,算法有两层循环实现,循环之前要初始化,第一层循环n次,第一层的含义是循环了几条边;第二层循环循环m次,更新距离,这里有个关键点就是要备份数组,用备份的数组去更新距离。
备份在第一与第二层循环之间备份。
- 例题:
-
代码:
#include <bits/stdc++.h> using namespace std; const int N = 510, M = 10010; int n,m,k; int dist[N];//意义跟上面dijkstra的一样 bool st[N]; int 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 ++ )//因为题目限制不超过k条边 { memcpy(backup,dist,sizeof dist);//一定要记得备份 for(int j = 0; j < m; j ++ ) { int a = edges[j].a; int b = edges[j].b; int w = edges[j].w; dist[b] = min(dist[b], backup[a] + w); } } if(dist[n] > 0x3f3f3f3f / 2) return -2; return dist[n]; } int main() { scanf("%d%d%d",&n,&m,&k); for(int i = 0; i < m; i ++ ) { int a,b,w; scanf("%d%d%d",&a,&b,&w); edges[i] = {a,b,w}; } int t = bellman_ford(); if(t == -2) puts("impossible"); else printf("%d\n",t); return 0; }
-
-
SPFA(基本可以解决dijkstra的题,但是如果卡住只能用堆优化的dijkstra;SPFA还可以判断是否有负权回路)
-
思路:
对bellman_ford的优化就是用队列实现只对需要更新的点进行更新,代码类似堆优化的dijkstra,只是st数组标注的是点是否已经在队列中,队列存的是待更新的点。
-
例题:
-
代码:
#include <bits/stdc++.h> using namespace std; const int N = 100010; int h[N],w[N],e[N],ne[N],idx; int dist[N]; bool st[N];//标志这个点是否已经在队列中 int n,m; 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 -2; return dist[n]; } int main() { scanf("%d%d",&n,&m); memset(h,-1,sizeof h); for(int i = 0; i < m; i ++ ) { int a,b,c; scanf("%d%d%d",&a,&b,&c); add(a,b,c); } int res = spfa(); if(res == -2) puts("impossible"); else printf("%d\n",res); return 0; }
-
-
Floyd:
-
思路:
基于动态规划,三维的数组实现。d[ i ] [ j ] [ k ],表示从 i 开始,经过1 - k个点到达 j 的最短距离,只是最后k被优化掉了,只剩下二维。
-
例题:
-
代码:
#include <bits/stdc++.h> using namespace std; const int N = 210, INF = 1e9; int n,m,k; 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,&k); 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;//这里的初始化有区别与上面几种算法,因为它是多源的 for(int i = 0; i < m; i ++ ) { int x,y,z; scanf("%d%d%d",&x,&y,&z); d[x][y] = min(d[x][y],z);//存最小的边即可 } floyd(); while(k -- ) { int x,y; scanf("%d%d",&x,&y); if(d[x][y] > INF / 2) puts("impossible");//因为有负的,所以只需大于 INF / 2 else cout << d[x][y] << endl; } return 0; }
-
以上是个人的一些看法,如果有错误的或者更好的做法,欢迎评论区给予更正谢谢!