当前位置: 首页 > news >正文

代码随想录算法训练营第60期第六十四天打卡

       大家好,我们昨天是讲解了最短路算法,我先告诉大家一句,我们最近图论里面尤其是并查集之后讲解的这些算法,比如最小生成树和算法与最短路算法大家有初步了解就可以了,目前不需要自己手撕代码,因为大家可能多是第一次接触这些算法,这些算法的逻辑有的比较难,而且代码量也比较大,大家就有一个初步了解即可,我们今天就继续我们昨天的内容继续讲,今天我们要讲的是SPFA算法其实就是队列优化算法和bellman_ford之判断负权回路,我们就开始今天的内容。

第一部分 Bellman_ford 队列优化算法(又名SPFA)

        我们先看到理论基础部分,大家对这个算法可能是不熟悉的,可能这个算法的名称还是第一次听说过,其实我也是第一次接触,我以前的基础也就到了Dijkstra算法和Floyd算法,这个算法我需要和大家一起学习,我们现在就开始。

      大家首先知道 SPFA 和 Bellman_ford 队列优化算法指的都是一个算法,大家可以发现 Bellman_ford 算法每次松弛 都是对所有边进行松弛。其实我们先看下面的这幅图:

       大家应该还记得我们的Bellman_ford算法是对所有的边进行松弛,我们起点是1,与起点有关联的两个节点就是只有2,3两个,因此我们只有松弛 边(节点1->节点2)和 边(节点1->节点3)才是有效的,我们的Bellman_ford算法是每一条边都松弛了其实无意间做了很多无用功,基于以上思路,如何记录 上次松弛的时候更新过的节点呢?这时候我们就应该使用队列,大家是否还记得我们前面使用广度优先搜索算法解决岛屿问题的时候是不是也是使用了队列存储我们目前访问的节点,这里也是使用队列,我们就看看我们是如何模拟出来的。

      我们依然使用minDist数组来表达 起点到各个节点的最短距离,例如minDist[3] = 5 表示起点到达节点3 的最小距离为5,首先进行初始化,起点为节点1, 起点到起点的最短距离为0,所以minDist[1] 为 0。 将节点1 加入队列 (下次松弛从节点1开始),

       我们其实目前的状态就是这样的,从队列里取出节点1,松弛节点1 作为出发点连接的边(节点1 -> 节点2)和边(节点1 -> 节点3),我们可以很轻易看出边:节点1 -> 节点2,权值为1 ,minDist[2] > minDist[1] + 1 ,更新 minDist[2] = minDist[1] + 1 = 0 + 1 = 1 。边:节点1 -> 节点3,权值为5 ,minDist[3] > minDist[1] + 5,更新 minDist[3] = minDist[1] + 5 = 0 + 5 = 5。这时候我们就把节点2与节点3加入队列,我就不再截图了,大家其实自己也是可以想出来的,随后从队列里取出节点2,松弛节点2 作为出发点连接的边(节点2 -> 节点4)和边(节点2 -> 节点5),边:节点2 -> 节点4,权值为1 ,minDist[4] > minDist[2] + (-3) ,更新 minDist[4] = minDist[2] + (-3) = 1 + (-3) = -2 。边:节点2 -> 节点5,权值为2 ,minDist[5] > minDist[2] + 2 ,更新 minDist[5] = minDist[2] + 2 = 1 + 2 = 3 。随后我们就将节点4与节点5加入队列,我们就重复上述过程即可。但是这里我们是从队列里出去节点3,松弛节点3 作为出发点连接的边。因为没有从节点3作为出发点的边,所以这里就从队列里取出节点3就好,不用做其他操作,其实往后都是一样的操作了,我们要使用一个visited数组来存储保证我当前的节点没有被访问过,这样我们就完成了基于队列优化bellman_ford的算法模拟过程。其实我们就可以发现SPFA算法可以省去很多不必要的操作。同时大家也可以发现 基于队列优化的算法,要比bellman_ford 算法 减少很多无用的松弛情况,特别是对于边数众多的大图 优化效果明显。

         这样我们就可以给出代码:

#include <iostream>
#include <vector>
#include <queue>
#include <list>
#include <climits>
using namespace std;struct Edge { //邻接表int to;  // 链接的节点int val; // 边的权重Edge(int t, int w): to(t), val(w) {}  // 构造函数
};int main() {int n, m, p1, p2, val;cin >> n >> m;vector<list<Edge>> grid(n + 1); vector<bool> isInQueue(n + 1); // 加入优化,已经在队里里的元素不用重复添加// 将所有边保存起来for(int i = 0; i < m; i++){cin >> p1 >> p2 >> val;// p1 指向 p2,权值为 valgrid[p1].push_back(Edge(p2, val));}int start = 1;  // 起点int end = n;    // 终点vector<int> minDist(n + 1 , INT_MAX);minDist[start] = 0;queue<int> que;que.push(start); while (!que.empty()) {int node = que.front(); que.pop();isInQueue[node] = false; // 从队列里取出的时候,要取消标记,我们只保证已经在队列里的元素不用重复加入for (Edge edge : grid[node]) {int from = node;int to = edge.to;int value = edge.val;if (minDist[to] > minDist[from] + value) { // 开始松弛minDist[to] = minDist[from] + value; if (isInQueue[to] == false) { // 已经在队列里的元素不用重复添加que.push(to);isInQueue[to] = true;}}}}if (minDist[end] == INT_MAX) cout << "unconnected" << endl; // 不能到达终点else cout << minDist[end] << endl; // 到达终点最短路径
}

          但是还队列优化版Bellman_ford 的时间复杂度 并不稳定,效率高低依赖于图的结构。如果是一个双向图,且每一个节点和所有其他节点都相连的话,那么该算法的时间复杂度就接近于 Bellman_ford 的 O(N * E) N 为节点数量,E为边的数量。大家目前了解这个算法就可以了。

第二部分 bellman_ford之判断负权回路

       我们来到今天的第二部分内容,我们通过以前的学习我们了解到几种最短路算法,比如Dijkstra算法和bellman_ford算法,我们知道Dijkstra算法是不能解决边权为负数的图中的最短路问题的,因此我们才有了bellman_ford算法,我们现在就要用bellman_ford判断负权回路,其实大家时候还记得我们前面的拓扑排序,我们是不是也是用拓扑排序来判断图中是否存在环,我们是利用入度来寻找起点的,如果出现了环就不存在入度为0的节点我们的拓扑排序就无法继续下去,我们一起来看看bellman_ford之判断负权回路的思路。

      我们首先需要先知道对于在有负权值的图中求最短路,都需要先看看这个图里有没有负权回路。因为 有负权回路 就是可以无限最短路径(一直绕圈,就可以一直得到无限小的最短距离)。因为是负权其实越走路径会越短,那么每松弛一次,都会更新最短路径,所以结果会一直有变化。因此我们在求解带负权的图中的最短路问题就需要先判断是否存在负权回路,

         大家可以先看到上面这幅图,这幅图是存在回路的,图中 节点1 到 节点4 的最短路径是多少(题目中的最低运输成本) (注意边可以为负数的)节点1 -> 节点2 -> 节点3 -> 节点4,这样的路径总成本为 -1 + 1 + 1 = 1,但是注意这里负权的回路,这就需要注意了,那么我们在负权回路中多绕一圈,我们的最短路径 是不是就更小了 (也就是更低的运输成本),我们多绕几圈路径就会更短,节点1 -> 节点2 -> 节点3 -> 节点1 -> 节点2 -> 节点3 -> 节点4,这样的路径总成本 (-1) + 1 + (-1) + (-1) + 1 + (-1) + 1 = -1,我们就会发现如果在负权回路多绕两圈,三圈,无穷圈,那么我们的总成本就会无限小, 如果要求最小成本的话,你会发现本题就无解了。这就是我们需要判断是否存在图中是否存在负权回路的原因,如果如果遇到这样的最短路问题就会出现无解的情况。

       在bellman_ford算法中,松弛 n-1 次所有的边 就可以求得 起点到任何节点的最短路径,松弛 n 次以上,minDist数组(记录起到到其他节点的最短距离)中的结果也不会有改变 。而在有负权回路的情况下,一直都会有更短的最短路,所以松弛 第n次,minDist数组也会发生改变。其实我们就可以再多松弛一次,看minDist数组 是否发生变化。我们对所有边松弛了n-1次后,在松弛一次,如果出现minDist出现变化就判断有负权回路。其实这就可以判断了,如果使用 SPFA 那么节点都是进队列的,那么节点进入队列几次后 足够判断该图是否有负权回路呢?其实也是可以的,那么如果节点加入队列的次数 超过了 n-1次 ,那么该图就一定有负权回路。大家目前理解这一些就可以了。

今日总结

       我们今天讲解了最短路算法的优化算法SPFA算法和判断负权回路的方法,大家理解好理论基础就可以了,无需深究,大家可以等到有了扎实的基础之后再去深究和手撕代码,我们今天就讲解到这里,我们明天再见!

相关文章:

  • 什么是数据转换?数据转换有哪些方式?
  • C++ 智能指针实现原理
  • 香橙派3B学习笔记9:Linux基础gcc/g++编译__C/C++中动态链接库(.so)的编译与使用
  • Mybatisplus3.5.6,用String处理数据库列为JSONB字段
  • 【CF】Day80——Codeforces Round 872 (Div. 2) C⭐D (思维 + 模拟 | 树 + 思维 + 组合数学 + 分数取模)
  • 未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?
  • 【valse2025】CV与ML领域重要进展
  • python打卡训练营打卡记录day50
  • 【Java工程师面试全攻略】Day7:分布式系统设计面试精要
  • 蓝牙 BLE 扫描面试题大全(2):进阶面试题与实战演练
  • 聊聊 Pulsar:Producer 源码解析
  • python打卡day50
  • 常见的http状态码
  • 重温经典算法——二分查找
  • Word中如何对文献应用的格式数字连起来,如:【1-3】
  • 【SQL学习笔记3】深入理解窗口函数的用法
  • Java SE - 数组
  • svg预览器
  • 嵌入式学习Day35
  • Debian系统简介
  • 芜湖市住房和城乡建设局官网/网站关键词排名优化客服
  • 网站开发一般会用到什么语言/电商运营培训学费多少
  • 做3d模型网站赚钱么/什么软件引流客源最快
  • 珠海企业建站程序/北京seo教师
  • 营销网站建设哪家便宜/营销软文推广平台
  • 云南网站开发报价/seo名词解释