2.3搜索与图论(最短路)
1.朴素Dijkstra算法
稠密图用邻接矩阵,稀疏图用邻接表存
1≤m≤10^5,所以显然这是个稠密图,由于图中可能存在重边和自环,于是我们用g[N][N]存储每条边的距离时,先要将其初始化为无穷,然后读取时比较一下其与原数组中的值的大小,取最小值存储。Dijkstra方法中,对于每个数都循环一次,先找到没有找到过的距离1点最近的点,把它的st数组中的值设为真,然后遍历所有数更新其距离。
#include <iostream> #include <cstring> #include <algorithm> using namespace std; const int N = 510; int g[N][N] , dist[N];//g[i][j]存储i到j的距离,dist表示每个点到1的距离 int n , m; bool st[N];//表示每个点是否能确定到1的距离 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] and (t = -1 or dist[j] < dist[t]))//j没有确定最短路并且(t没有复制或者t并不是最短的) 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() { cin >> n >> m; memset(g , 0x3f , sizeof g);//初始化成很大的数 while (m--) { int a , b , c; scanf("%d%d%d" , &a , &b , &c); g[a][b] = min(c , g[a][b]); } cout << dijkstra() <<endl; return 0; }
2.用堆实现的Dijkstra算法
用新开一个数组w[N]表示权重,有了新的add方法的写法。
用priority_queue<PII , vector<PII> , greater<PII>>即小根堆存储,因为每次循环过后,找的是除了能确定距离的点外距离最小的点,将其的st数组值转为可以确定距离,多以用小根堆从小到大排列的特性可以非常方便的找到那个点。
priority_queue<PII , vector<PII> , greater<PII>>第一个数是距离,因为要用距离来排,第二个数是代表这是第几个点。如果队列不空就一直循环,先把队头取出,如果队头代表的点已经取出过了,就continue取下一个点;如果没有更新一下布尔数组,再对与其相连的点进行枚举,如果现存的距离比dist[k]加两点相连的距离大就表示dist数组可以更新,并且其距离已经确定,加入小根堆中。
#include <iostream> #include <cstring> #include <queue> using namespace std; typedef pair<int , int> PII; const int N = 150010; int h[N] , e[N] , ne[N] , w[N] , idx;//采用邻接表来存储,不是g[N][N]邻接矩阵 int dist[N]; bool st[N]; int n , m; 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;//编号为1 的点距离起点的距离为0; priority_queue<PII , vector<PII> , greater<PII>> heap;//设置小根堆 heap.push({0,1});//小根堆要根据距离来排,即第一个数是距离,第二个数是他是哪个点 //插入1号点的距离为1 while (heap.size())//当堆不空 { auto t = heap.top();//取出队头并删除队头 heap.pop(); int k = t.second , distance = t.first;//t是第k个点,到起点距离为distance if (st[k]) continue;//此点冗余 st[k] = true; for (int i = h[k] ; i != -1 ; i = ne[i])//循环 { int j = e[i]; if (dist[j] > dist[k] + w[i])//如果dist值可以更新 { dist[j] = dist[k] + w[i];//那就更新,并将其加入堆 heap.push({dist[j] , j}); } } } if(dist[n] == 0x3f3f3f3f) return -1; return dist[n]; } int main() { cin >> n >> m; memset(h , -1 , sizeof h);//常规操作,把表头指向-1; while(m--) { int a, b ,c; scanf("%d%d%d" , &a , &b , &c); add(a , b , c); //采用堆就不用考虑重边的问题 } cout << dijkstra() <<endl; return 0; }
3. Bellman Ford算法
处理最多经过k条边,拥有负权回路的最短路
先枚举k次操作,每次操作枚举m条边,看看能不能更新
代码如下:
#include <iostream> #include <cstring> #include <algorithm> const int N = 510 , M = 10010; int dist[N] , backup[N]; using namespace std; struct Edge{ //结构体 int a , b , w; }edges[M]; int n , m , k; void 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 , b = edges[j].b , w = edges[j].w; dist[b] = min(dist[b] , backup[a] + w); } } } int main() { cin >> n >> m >> k; int i = 0; for (int i = 0; i < m; i ++ ) { int a , b , w; scanf("%d%d%d" , &a , &b , &w); edges[i] = {a , b , w}; } bellman_ford(); if (dist[n] > 0x3f3f3f3f / 2) puts("impossible"); //如果dist[n]依然很大 else cout << dist[n] << endl; return 0; }
4. spfa算法
代码跟堆优化的dijkstra比较相似,y总推荐使用
说很多正权边的最短路本来是用dijkstra算法,事实上可能spfa也可以过
处理边权可能为负数,不存在负权回路的最短路
代码如下:
#include <iostream> #include <cstring> #include <algorithm> #include <queue> using namespace std; const int N = 100010; int h[N] , e[N] , w[N] , ne[N] , idx; int n , m; int dist[N]; bool st[N]; //存储每个数在不在队列q中 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); while (q.size()) { int t = q.front(); st[t] = false; //t出队,将其状态变更 q.pop(); 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]) //如果j不在队列中 { st[j] = true; //将j加入队列 q.push(j); } } } } } int main() { cin >> n >> m; memset(h, -1, sizeof h); while (m -- ) { int a , b , c; scanf("%d%d%d" , &a , &b , &c); add(a , b , c); } spfa(); if (dist[n] == 0x3f3f3f3f) puts("impossible"); else cout << dist[n] << endl; return 0; }
4.1 spfa算法判负环
跟spfa本体大差不差
#include <iostream> #include <cstring> #include <algorithm> #include <queue> using namespace std; const int N = 2010 , M = 10010; int h[N] , e[M] , ne[M] , w[M] , idx; int dist[N] , cnt[N]; bool st[N]; //该数是否进队列 int n , m; void add(int a, int b, int c) { e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ; } bool spfa() { queue<int>q; //并不是求1到n的路径有没有负环,有可能1到不了,所以把所有点加进去 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; //如果到j的路径经过大于等于n个点 if (!st[j]) { st[j] = true; q.push(j); } } } } return false; } int main() { cin >> n >> m; memset(h, -1, sizeof h); while (m -- ) { int a , b , c; scanf("%d%d%d" , &a , &b , &c); add(a, b, c); } if (spfa()) puts("Yes"); else puts("No"); return 0; }
5. Floyd多源汇最短路
代码挺简单,三重循环
#include <iostream> #include <cstring> #include <algorithm> using namespace std; const int N = 210; int g[N][N]; int n , m , k; void floyd() { for (int k = 1 ; k <= n ; k ++) //三重循环 for (int i = 1 ; i <= n ; i ++) for (int j = 1 ; j <= n ; j ++) g[i][j] = min(g[i][j] , g[i][k] + g[k][j]); } int main() { cin >> n >> m >> k; memset(g , 0x3f , sizeof g); //除了相同的点,其余边全部初始化成无穷 for (int i = 1 ; i <= n ; i ++) g[i][i] = 0; while (m -- ) { int a , b , c; scanf("%d%d%d" , &a , &b , &c); g[a][b] = min(g[a][b] , c); //读入所有边 } floyd(); while (k --) { int a , b; scanf("%d%d" , &a , &b); if (g[a][b] > 0x3f3f3f3f / 2) puts("impossible"); else printf("%d\n" , g[a][b]); } return 0; }
本文作者:乐池
本文链接:https://www.cnblogs.com/ratillase/p/15968850.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步