当前位置:   article > 正文

图论之最短路问题(acwing模板篇)_图论最短路样例

图论最短路样例

目录

朴素的Dijkstra算法:

Dijkstra模板:

 例题:Dijkstra求最短路

堆优化的dijkstra算法

例题:Dijkstra求最短路Ⅱ

Bellman-Ford算法(处理有负权边的图)

 例题:有边数限制的最短路

spfa算法(Bellman-Ford算法的队列优化算法)

spfa求负环

Floyd算法(多源最短路)


名词解释:n:通常代表点数                         m:通常代表边数

源:起点                   单源:一个起点                   多源:多个起点

汇点:终点

入度:有多少条边指向我

出度:以这个点为起点出去的边数

边权:离散数学或数据结构中,图的每条边上带的一个数值,他代表的含义可以是长度等等,这个值就是边权

框架图:

朴素的Dijkstra算法:

由于它的时间复杂度是O(N^2)边数与时间复杂度无关,所以它适合做稠密图

稠密图:边数是点数的2~3倍

ACwing算法基础课全程笔记(2021年8月12日开始重写+优化)_hebtu_Kangweiqi的博客-CSDN博客_acwing笔记算法基础课笔记,请支持正版https://blog.csdn.net/hebtu_Kangweiqi/article/details/109124329借用大佬的图= =

Dijkstra模板:

98504cdcddd6d683a91c09357395d12.png

 例题:Dijkstra求最短路

给定一个n个点m条边的有向图,图中可能存在重边和自环,所有边权均为正值。

请你求出1号点到n号点的最短距离,如果无法从1号点走到n号点,则输出-1

 思路解析:有贪心的感觉,每次我都选择离我距离最小的那条走,然后最1~j的最短距离就变成

1~t点的距离 t~j的最短距离

 建议自己看着代码自己在图上模拟一下,会感受到这个算法之妙!!!

  1. #include<iostream>
  2. using namespace std;
  3. const int N = 510;
  4. int n, m;
  5. int g[N][N];//采用邻接矩阵存储
  6. int dist[N];//距离数组
  7. bool st[N];//判断数组
  8. int dijkstra()
  9. {
  10. memset(dist, 0x3f, sizeof dist);//初始化数组的值为一个很大的数1e9
  11. dist[1] = 0;//将第1号点的到自身的距离初始化为0
  12. for (int i = 0; i < n; i++)
  13. {//n次迭代
  14. int t = -1;//随便的一个没有意义的数即可:目的是下面的寻找出2~n内距离点1最小的数
  15. for (int j = 1; j <= n; j++)//从1开始遍历:注意这个细节
  16. {
  17. if (!st[j] && (t == -1 || dist[j] < dist[t]))//如果该点未被走过
  18. {
  19. t = j;//相当于寻找最小值 1~1 1~2
  20. }
  21. }
  22. st[t] = 1;//标记已经用过了这个点
  23. for (int j = 1; j <= n; j++)//遍历1号点到n号点
  24. {//更新距离
  25. dist[j] = min(dist[j], dist[t] + g[t][j]);//1~j点的距离变为了:1~t的距离+t~j的距离
  26. }
  27. }
  28. if (dist[n] == 0x3f3f3f3f) return -1;//如果n点数组没有被用到过,那么证明走不到n点
  29. else return dist[n];
  30. }
  31. int main() {
  32. cin >> n >> m;
  33. memset(g, 0x3f, sizeof g);
  34. while (m--)
  35. {
  36. int x, y, z;//x~y的距离为 z
  37. cin >> x >> y >> z;
  38. g[x][y] = min(g[x][y], z);
  39. }
  40. int t = dijkstra();
  41. printf("%d\n", t);
  42. return 0;
  43. }

堆优化的dijkstra算法

98504cdcddd6d683a91c09357395d12.png

 通过对上图的分析:我们可以对2的(1)(3)用堆进行优化从而降低时间复杂度,但是会引入边数这个变量,所以堆优化版的dijkstra版适合做稀疏图

例题:Dijkstra求最短路Ⅱ

问题:

给定一个n个点m条边的有向图,图中可能存在重边和自环,所有边权均为非负值。

请你求出1号点到n号点的最短距离,如果无法从1号点走到n号点,则输出-1。

针对稀疏图,我们采用邻接表法

代码实现:

  1. #include<iostream>
  2. #include <queue>
  3. using namespace std;
  4. const int N = 1e6 + 10;
  5. typedef pair<int, int> PII;
  6. int h[N], e[2 * N], ne[2 * N], w[N], idx;//邻接表+权重数组
  7. int n, m;//点数+边数
  8. int dist[N];//距离数组
  9. bool st[N];//判断数组
  10. void add(int a, int b, int c)
  11. {
  12. e[idx] = b;
  13. ne[idx] = h[a];
  14. w[idx] = c;
  15. h[a] = idx++;
  16. }
  17. int dijkstra()
  18. {
  19. memset(dist, 0x3f, sizeof dist);//初始化距离为一个很大的数
  20. dist[1] = 0;//1到自身的距离为0
  21. priority_queue<PII, vector<PII>, greater<PII> >heap;//建立小根堆(优先队列)
  22. heap.push({ 0,1 });//入堆
  23. while (heap.size())//当队列不为空
  24. {
  25. auto t = heap.top();//取头
  26. heap.pop();//删头
  27. int ver = t.second, distance = t.first;//first:距离,ver:节点数
  28. if (st[ver]) continue;//如果已经标记过的节点,则不走
  29. st[ver] = 1;//标记节点
  30. for (int i = h[ver]; i != -1; i = ne[i])//邻接表的遍历
  31. {
  32. int j = e[i];//获取节点数
  33. if (dist[j] > distance + w[i])//求最小距离
  34. {
  35. dist[j] = distance + w[i];//节点1的到节点j的距离就是头节点的距离+它存的权重(边长)
  36. heap.push({ dist[j],j });//入堆
  37. }
  38. }
  39. }
  40. if (dist[n] == 0x3f3f3f3f) return -1;
  41. return dist[n];
  42. }
  43. int main() {
  44. cin >> n >> m;
  45. memset(h, -1, sizeof h);
  46. while (m--)
  47. {
  48. int x, y, z;
  49. cin >> x >> y >> z;
  50. add(x, y, z);
  51. }
  52. cout << dijkstra();
  53. }

Bellman-Ford算法(处理有负权边的图)

1、什么是bellman - ford算法?
Bellman - ford算法是求含负权图的单源最短路径的一种算法,效率较低,代码难度较小。其原理为连续进行松弛,在每次松弛时把每条边都更新一下,若在n-1次松弛后还能更新,则说明图中有负环,因此无法得出结果,否则就完成。
(通俗的来讲就是:假设1号点到n号点是可达的,每一个点同时向指向的方向出发,更新相邻的点的最短距离,通过循环n-1次操作,若图中不存在负环,则1号点一定会到达n号点,若图中存在负环,则在n-1次松弛后一定还会更新)

2、bellman - ford算法的具体步骤
for n次
for 所有边 a,b,w (松弛操作)
dist[b] = min(dist[b],back[a] + w)

注意:back[]数组是上一次迭代后dist[]数组的备份,由于是每个点同时向外出发,因此需要对dist[]数组进行备份,若不进行备份会因此发生串联效应,影响到下一个点

3、在下面代码中,是否能到达n号点的判断中需要进行if(dist[n] > INF/2)判断,而并非是if(dist[n] == INF)判断,原因是INF是一个确定的值,并非真正的无穷大,会随着其他数值而受到影响,dist[n]大于某个与INF相同数量级的数即可


4、bellman - ford算法擅长解决有边数限制的最短路问题
时间复杂度 O ( n m ) O(nm)O(nm)
其中n为点数,m为边数
————————————————
版权声明:本文为CSDN博主「云算法」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/hebtu_Kangweiqi/article/details/109124329

 例题:有边数限制的最短路

问题:

给定一个n个点m条边的有向图,图中可能存在重边和自环, 边权可能为负数。

请你求出从1号点到n号点的最多经过k条边的最短距离,如果无法从1号点走到n号点,输出impossible        注意:图中可能 存在负权回路

  1. #include <iostream>
  2. #include <cstring>
  3. using namespace std;
  4. const int N = 1e4 + 50;
  5. int n, m, k;
  6. int dis[N], backup[N];
  7. struct edge { int a, b, w; } e[N];
  8. void bellman()
  9. {
  10. memset(dis, 0x3f, sizeof dis);
  11. dis[1] = 0;
  12. for (int i = 0; i < k; i ++)//最多经过 k 条边
  13. {
  14. memcpy(backup, dis, sizeof dis);//每次用上一次的状态去更新
  15. for (int j = 1; j <= m; j ++)
  16. {
  17. int a = e[j].a, b = e[j].b, w = e[j].w;
  18. dis[b] = min(dis[b], backup[a] + w);//到b点的距离相当于a点的距离加上边长
  19. }
  20. }
  21. }
  22. int main()
  23. {
  24. cin >> n >> m >> k;
  25. for (int i = 1; i <= m; i ++)
  26. cin >> e[i].a >> e[i].b >> e[i].w;
  27. bellman();
  28. //是否能到达n号点的判断中需要进行if(dist[n] > INF/2)判断,而并非是if(dist[n] == INF)判断,原因是INF是一个确定的值,并非真正的无穷大,会随着其他数值而受到影响,dist[n]大于某个与INF相同数量级的数即可
  29. if (dis[n] > 0x3f3f3f3f / 2) puts("impossible");
  30. else cout << dis[n] << endl;
  31. return 0;
  32. }

spfa算法(Bellman-Ford算法的队列优化算法)

  1. spfa算法步骤
  2. queue <1
  3. while queue 不为空
  4. (1) t <– 队头
  5. queue.pop()
  6. (2)用 t 更新所有出边 t –> b,权值为w
  7. queue <– b (若该点被更新过,则拿该点更新其他点)

时间复杂度 一般:O ( m ) 最坏:O ( n m )
n为点数,m为边数

3、spfa也能解决权值为正的图的最短距离问题,且一般情况下比Dijkstra算法还好
————————————————
版权声明:本文为CSDN博主「云算法」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/hebtu_Kangweiqi/article/details/109124329

问题:

有边数限制的最短路

给定一个n个点m条边的有向图,图中可能存在重边和自环, 边权可能为负数。

请你求出从1号点到n号点的最多经过k条边的最短距离,如果无法从1号点走到n号点,输出impossible。

注意:图中可能 存在负权回路 。

  1. #include<iostream>
  2. #include<queue>
  3. using namespace std;
  4. const int N = 1e5 + 5;
  5. int n, m, dis[N];
  6. bool st[N];//代表点是否在队列中
  7. int h[N], e[N], ne[N], w[N], idx;//单链表
  8. void add(int a, int b, int c)//单链表的插入操作
  9. {
  10. e[idx] = b;
  11. ne[idx] = h[a];
  12. w[idx] = c;
  13. h[a] = idx++;
  14. }
  15. int spfa()
  16. {
  17. queue<int> q;
  18. memset(dis, 0x3f, sizeof dis);
  19. q.push(1);//放入节点1
  20. dis[1] = 0;//距离为1
  21. st[1] = 1;//判断
  22. while (!q.empty()) //队列不为空
  23. {
  24. int u = q.front();//取出头
  25. q.pop();//删头
  26. st[u] = 0;//标记
  27. for (int i = h[u]; i != -1; i = ne[i])//遍历链表
  28. {
  29. int v = e[i];//取出节点
  30. if (dis[v] > dis[u] + w[i])//求最小值
  31. {
  32. dis[v] = dis[u] + w[i];
  33. if (!st[v]) //如果没有走过
  34. {
  35. q.push(v);//放入
  36. st[v] = 1;//标记
  37. }
  38. }
  39. }
  40. }
  41. if (dis[n] == 0x3f3f3f3f) return -1;
  42. else return dis[n];
  43. }
  44. int main() {
  45. memset(h, -1, sizeof h);
  46. scanf("%d%d", &n, &m);
  47. while (m--) {
  48. int x, y, z;
  49. scanf("%d%d%d", &x, &y, &z);
  50. add(x, y, z);
  51. }
  52. int t = spfa();
  53. if (t == -1) puts("impossible");
  54. else printf("%d", t);
  55. }

spfa求负环

问题:

给定一个n个点m条边的有向图,图中可能存在重边和自环, 边权可能为负数

请你判断图中是否存在负权回路

  1. #include<iostream>
  2. #include<queue>
  3. using namespace std;
  4. const int N = 2005, M = 10005;
  5. int h[N], e[M], ne[M], w[M], idx, n, m;
  6. int dis[N], cnt[N];//需要额外一个cnt数组,记录当前点最短路上的边数
  7. bool st[N];
  8. void add(int a, int b, int c)
  9. {
  10. e[idx] = b;
  11. w[idx] = c;
  12. ne[idx] = h[a];
  13. h[a] = idx++;
  14. }
  15. bool spfa()
  16. {
  17. /*首先距离不需要初始化*/
  18. queue<int> q;
  19. /*将所有点入队,可以防止有的点不能走到负环*/
  20. for (int i = 1; i <= n; i++)
  21. {
  22. q.push(i);
  23. st[i] = 1;
  24. }
  25. while (!q.empty())
  26. {
  27. int u = q.front();
  28. q.pop();
  29. st[u] = 0;
  30. for (int i = h[u]; i != -1; i = ne[i])
  31. {
  32. int v = e[i];
  33. if (dis[v] > dis[u] + w[i])
  34. {
  35. dis[v] = dis[u] + w[i];
  36. cnt[v] = cnt[u] + 1;
  37. if (cnt[v] >= n) return 1;
  38. /*如果超过n,根据抽屉原理,中间经过的点数一定大于n,*/
  39. if (!st[v]) {
  40. q.push(v);
  41. st[v] = 1;
  42. }
  43. }
  44. }
  45. }
  46. return 0;
  47. }
  48. int main() {
  49. scanf("%d%d", &n, &m);
  50. memset(h, -1, sizeof h);
  51. for (int i = 0; i < m; i++) {
  52. int x, y, z;
  53. scanf("%d%d%d", &x, &y, &z);
  54. add(x, y, z);
  55. }
  56. if (spfa()) puts("Yes");
  57. else puts("No");
  58. }

Floyd算法(多源最短路)

问题:

给定一个n个点m条边的有向图,图中可能存在重边和自环,边权可能为负数。

再给定k个询问,每个询问包含两个整数x和y,表示查询从点x到点y的最短距离,如果路径不存在,则输出“impossible”。

数据保证图中不存在负权回路

  1. #include<iostream>
  2. using namespace std;
  3. const int N = 210;
  4. const int inf = 0x3f3f3f3f;
  5. int n, m, q;
  6. int d[N][N];
  7. void floyd() {
  8. for (int k = 1; k <= n; k++)
  9. {
  10. for (int i = 1; i <= n; i++)
  11. {
  12. for (int j = 1; j <= n; j++)
  13. {
  14. d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
  15. }
  16. }
  17. }
  18. }
  19. int main() {
  20. scanf("%d%d%d", &n, &m, &q);
  21. /*初始化*/
  22. for (int i = 1; i <= n; i++)
  23. {
  24. for (int j = 1; j <= n; j++)
  25. {
  26. if (i == j) d[i][j] = 0;
  27. else d[i][j] = inf;
  28. }
  29. }
  30. while (m--)
  31. {
  32. int x, y, z;
  33. scanf("%d%d%d", &x, &y, &z);
  34. d[x][y] = min(d[x][y], z);//注意重边
  35. }
  36. floyd();
  37. while (q--) //q次询问
  38. {
  39. int x, y;
  40. scanf("%d%d", &x, &y);
  41. if (d[x][y] > inf / 2) puts("impossible");//题目可能会出现负权边,所以还要应用之前的套路
  42. else printf("%d\n", d[x][y]);
  43. }
  44. return 0;
  45. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/243580
推荐阅读
相关标签
  

闽ICP备14008679号