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

5.图论.题目3

5.图论.题目3

  • 题目
    • 15.软件构造/BFS拓扑搜索
    • 16.参加科学大会/dijkstra 算法
    • 17.城市货物运输1/bellman_ford 算法
    • 18.城市运输货物2/bellman_ford 算法
    • 19.城市运输货物3/bellman_ford 算法
  • 最短路径问题总结

题目

15.软件构造/BFS拓扑搜索

题目连接
在这里插入图片描述
概括来说,给出一个 有向图,把这个有向图转成线性的排序 就叫拓扑排序。当然拓扑排序也要检测这个有向图 是否有环,即存在循环依赖的情况,因为这种情况是不能做线性排序的。
实现拓扑排序的算法有两种:卡恩算法(BFS)和DFS;这题要使用卡恩算法(BFS)实现,这就类型广度优先搜索。
BFS算法代码:

#include <iostream>
#include "vector"
#include "queue"
#include "unordered_map"using namespace std;int main(){int m,n,s,t;cin>> n>> m;vector<int> indegree(n,0);unordered_map<int, vector<int>> graph;for(int i=0;i<m;i++){cin>> s>> t;graph[s].push_back(t);indegree[t]++;}queue<int> q;for(int i=0; i<n; i++){if(indegree[i]==0) q.push(i);}vector<int> res;// BFSwhile (!q.empty()){int cur = q.front();q.pop();res.push_back(cur);vector<int> files = graph[cur];if(!files.empty()){// cur有被依赖的文件for(int file : files) {indegree[file]--;if (indegree[file] == 0) q.push(file); // 将新的入度为0的节点放入que中}}}if(res.size() == n){for(int i=0;i<n-1;i++){cout<< res[i]<<" ";}cout<< res[n-1];}else cout<< -1<< endl;return 0;
}

16.参加科学大会/dijkstra 算法

题目链接
在这里插入图片描述
本题就是求最短路,最短路是图论中的经典问题即:给出一个有向图,一个起点,一个终点,问起点到终点的最短路径。这题可以使用dijkstra 算法解决。
dijkstra算法:在有权图(权值非负数)中求从起点到其他节点的最短路径算法,需要注意两点:

  • dijkstra算法可以同时求 起点到所有节点的最短路径
  • 权值不能为负数
    在这里插入图片描述

17.城市货物运输1/bellman_ford 算法

题目链接
在这里插入图片描述
解决经典的带负权值的单源最短路问题,此时就轮到Bellman_ford登场了。
共有两个关键点。

  1. “松弛”究竟是个啥?
  2. 为什么要对所有边松弛 n - 1 次 (n为节点个数) ?

在这里插入图片描述
B节点的mindist值可以由A,C节点值推导出来,状态一: minDist[A] + value 可以推出 minDist[B] 状态二: minDist[B]本身就有权值 (可能是其他边链接的节点B 例如节点C,以至于 minDist[B]记录了其他边到minDist[B]的权值)
如果 通过 A 到 B 这条边可以获得更短的到达B节点的路径,这就是松弛的概念;而在路径搜索中这成为剪枝操作;也是采用了动态规划的思想,即:将一个问题分解成多个决策阶段,通过状态之间的递归关系最后计算出全局最优解。

if (minDist[B] > minDist[A] + value) minDist[B] = minDist[A] + value

对所有边松弛一次,相当于计算 起点到达 与起点一条边相连的节点 的最短距离
节点数量为n,那么起点到终点,最多是 n-1 条边相连。那么无论图是什么样的,边是什么样的顺序,我们对所有边松弛 n-1 次 就一定能得到 起点到达 终点的最短距离。

普通版Bellman_ford算法

#include <iostream>
#include "vector"
#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 m,n,p1,p2,val;cin>> n>> m;vector<vector<Edge>> graph(n+1); // 邻接表,但其实可以使用邻接矩阵进行存储图for(int i=0; i<m; i++){cin>> p1>> p2>> val;graph[p1].emplace_back(p2, val);}vector<int> mindist(n+1, INT_MAX);mindist[1]=0;// n-1次松弛for(int j=1; j<n; j++){// 遍历邻接表所有的边for(int i=1; i<=n; i++){for(auto &e: graph[i]){int from = i;int to = e.to;int price = e.val;if(mindist[from]!=INT_MAX && mindist[to]>mindist[from]+price) mindist[to] = mindist[from]+price;}}cout<< "松弛第 "<< j<< "次"<< endl;for(int k=1; k<=n; k++){cout<< mindist[k]<< " ";}cout<< endl;}if(mindist[n]==INT_MAX) cout<< "unconnected"<< endl;else cout<< mindist[n]<< endl;return 0;
}

队列优化版Bellman_ford算法
Bellman_ford 队列优化算法(Queue improved Bellman-Ford) ,也叫SPFA算法(Shortest Path Faster Algorithm)。大家可以发现 Bellman_ford 算法每次松弛 都是对所有边进行松弛。但真正有效的松弛,是基于已经计算过的节点在做的松弛
基于以上思路,==如何记录上次松弛的时候更新过的节点呢?==用队列来记录。(其实用栈也行,对元素顺序没有要求)
基于队列优化的算法,要比bellman_ford 算法 减少很多无用的松弛情况,特别是对于边数众多的大图 优化效果明显

    while (!que.empty()) {int node = que.front(); que.pop();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;que.push(to);}}}

Bellman_ford队列优化版 的时间复杂度 并不稳定,效率高低依赖于图的结构;
例如 如果是一个双向图,且每一个节点和所有其他节点都相连的话,那么该算法的时间复杂度就接近于 Bellman_ford 的 O(N * E) N 为节点数量,E为边的数量。
所以如果图越稠密,则 SPFA的效率越接近与 Bellman_ford。反之,图越稀疏,SPFA的效率就越高


18.城市运输货物2/bellman_ford 算法

题目链接
在17题的基础上,条件在评估从城市 1 到城市 n 的所有可能路径中综合政府补贴后的最低运输成本时,存在一种情况:图中可能出现负权回路
负权回路是指一系列道路的总权值为负,这样的回路使得通过反复经过回路中的道路,理论上可以无限地减少总成本或无限地增加总收益。
为了避免在使用Bellman_ford算法遇到该情况时,出现这种异常情况,需要插入一个判断环节,返回这种异常情况。
在这里插入图片描述
在 bellman_ford 算法中,松弛 n-1 次所有的边 就可以求得 起点到任何节点的最短路径,松弛 n 次以上,minDist数组(记录起到到其他节点的最短距离)中的结果也不会有改变;而在有负权回路的情况下,一直都会有更短的最短路,所以 松弛 第n次,minDist数组也会发生改变。

那么很自然,我们在基础版的还是队列优化版的bellman_ford算法松弛n-1次的基础上,再额外松弛一次,判断mindist数组中是否发生改变,若有发生改变,则图中必定存在负权回路。

  • 基础版bellman_ford:n个节点,最多n-1条边即可保证起点1至n连通,所以bellman_ford松弛n-1次即可得到到达n节点的距离最小路径。。在顶层for循环中增加一次for循环,在最后的循环中检查mindist数组是否满足松弛条件
  • 队列优化版bellman_ford:在极端情况下,即:所有节点都与其他节点相连,每个节点的入度为 n-1 (n为节点数量),所以每个节点最多加入 n-1 次队列。在while循环下,检查松弛条件生效时,全局计数器数组对应节点下标加1,如果存在某节点加入队列次数超过 n-1次 就说明该图与负权回路。

以队列优化版Bellman_ford算法为例子:

#include <iostream>
#include "vector"
#include "queue"
#include "climits"using namespace std;struct Edge {int to;  // 链接的节点int val; // 边的权重Edge(int t, int w): to(t), val(w) {}  // 构造函数
};int main(){int m,n,p1,p2,val;cin>> n>> m;vector<vector<Edge>> graph(n+1); // 邻接表for(int i=0; i<m; i++){cin>> p1>> p2>> val;graph[p1].emplace_back(p2, val);}vector<int> mindist(n+1, INT_MAX);mindist[1]=0;queue<int> q; // 队列存放待遍历的节点q.push(1);vector<int> count(n+1, 0); // 记录每个节点的入队次数count[1]++;bool flag = false;while(!q.empty()){int node = q.front(); q.pop();for(auto edge: graph[node]){int from = node;int to = edge.to;int value = edge.val;if(mindist[to]>mindist[from]+value){mindist[to] = mindist[from]+value;q.push(to);count[to]++;if(count[to]==n) {flag = true;while(!q.empty()) q.pop(); //释放break;}}}}if(flag) cout<< "circle"<< endl;else if(mindist[n]==INT_MAX) cout<< "unconnected"<< endl;else cout<< mindist[n]<< endl;return 0;
}

19.城市运输货物3/bellman_ford 算法

题目链接
在17题的基础上,解决经典的带负权值的单源最短路问题,考虑最多经过 k 个城市的条件下,而不是一定经过k个城市,也可以经过的城市数量比k小,但要最短的路径。
最多经过k个节点,即最多经过k+1个节点到达终点的最短距离。此时在使用普通版的bellman_ford 算法时,处理带负权值回路的情况。
在这里插入图片描述
根据普通版代码每次顶层for循环遍历所有边可知,以上mindist是普通版bellman_ford算法得到第一次松弛的结果;所有边进行的第二次松弛,minDist数组为 : -2 -2 -1 0 所有边进行的第三次松弛,minDist数组为 : -3 -3 -2 -1等。
理论上来说,对所有边松弛一次,相当于计算 起点到达 与起点一条边相连的节点 的最短距离。但我们在实际代码运行发现不仅仅 与起点一条边相连的节点更新了,所有节点都更新了。这说明至多经过k个节点这个限制没有起作用。因为理论上节点三最快在对所有边第二次松弛时,才会更新,而在第一次松弛时,当时是基于已经计算好的 节点2(minDist[2])来做计算了。

所以在每次计算 minDist 时候,要基于 对所有边上一次松弛的 minDist 数值才行,所以我们要记录上一次松弛的minDist(直观的想法是每次顶层遍历时先拷贝上一次计算的结果)

    for (int i = 1; i <= k + 1; i++) {minDist_copy = minDist; // 获取上一次计算的结果for (vector<int> &side : grid) {int from = side[0];int to = side[1];int price = side[2];// 注意使用 minDist_copy 来计算 minDist if (minDist_copy[from] != INT_MAX && minDist[to] > minDist_copy[from] + price) {  minDist[to] = minDist_copy[from] + price;}}}

时间复杂度: O(K * E) , K为至多经过K个节点,E为图中边的数量;空间复杂度: O(N) ,即 minDist 数组所开辟的空间

本题本质
在之前17,18题没有使用mindist_copy拷贝上一次最小节点距离数组怎么会没有影。在17题,是没有负权回路的,那么无论比n-1多松弛多少次,mindist结果不会发生改变。因此在对所有边进行第一次松弛的时候,如果基于 本次计算的 minDist 来计算 minDist (相当于多做松弛了),也是对最终结果没影响。在18题,是判断是否有 负权回路,一旦有负权回路,则异常返回。
因此本题的关键区别是:
1.可以有负权回路,说明只要多做松弛,结果是会变的。
2.要求最多经过k个节点,对松弛次数是有限制的。

SPFA
要求最多经过k个节点,对松弛次数是有限制的。使用技巧,可以用一个变量 que_size 记录每一轮松弛入队列的所有节点数量;下一轮松弛的时候,就把队列里 que_size 个节点都弹出来,就是上一轮松弛入队列的节点。

    int que_size;while (k-- && !que.empty()) {minDist_copy = minDist; // 获取上一次计算的结果que_size = que.size(); // 记录上次入队列的节点个数while (que_size--) { // 上一轮松弛入队列的节点,这次对应的边都要做松弛int node = que.front(); que.pop();for (Edge edge : grid[node]) {int from = node;int to = edge.to;int price = edge.val;if (minDist[to] > minDist_copy[from] + price) {minDist[to] = minDist_copy[from] + price;que.push(to);}}}}

理论上,SPFA的时间复杂度不是要比 bellman_ford 更优吗?因为在该过程中queue的进出元素操纵,耗时很大,所以同的时间复杂度的情况下,SPFA 实际上更耗时了。

能否使用dijkstra算法?
在这里插入图片描述
此时最多经过2个节点的搜索就完毕了,但结果中minDist[7] (即节点7的结果)并没有被更;使用dijkstra算法所遍历的节点是1-->2-->3-->4,dijkstra每一步都采取贪心的策略,以最小的代价值到达下一个没访问的节点,因此没办法找到1-->2-->6-->7这条路径。

最短路径问题总结

至此已经讲解了四大最短路算法,分别是Dijkstra、Bellman_ford、SPFA 和 Floyd
在这里插入图片描述
注意:因为A * 属于启发式搜索,和上面最短路算法并不是一类,不适合一起对比,所以没有放在一起

大体使用场景的分析

  1. 如果遇到单源且边为正数的情况,直接Dijkstra 算法。至于 使用朴素版还是 堆优化版 还是取决于图的稠密度, 多少节点多少边算是稠密图,多少算是稀疏图,一般可以直接用堆优化版本。
  2. 如果遇到单源边可为负数,直接 Bellman-Ford 算法,使用普通版还是队列优化版取决于图的稠密度
  3. 如果是遇到多源点且边为正求最短路,直接 Floyd 算法
  4. 对于A * ,由于其高效性,所以在实际工程应用中使用最为广泛 ,由于其 结果的不唯一性,也就是可能是次短路的特性,一般不适合作为算法题

http://www.mrgr.cn/news/17704.html

相关文章:

  • 互联网摸鱼日报(2024-09-02)
  • 这才是老板喜欢的运营简历
  • 微机消谐装置在出厂前是需要做哪些测试实验
  • asp.net Temporary ASP.NET Files修改为其他位置
  • WPS 5亿用户受威胁:APT-C-60利用WPS Office漏洞发动间谍攻击
  • 人体图像生成
  • 2024 年 8 大最佳 SD 卡恢复软件 |100% 工作
  • Jsoncpp的安装与使用
  • javaSSMmysql宠物领养系统的设计与实现26292-计算机毕业设计项目选题推荐(附源码)
  • 如何利用桃宝详情 api 接口数据实现个性化商品推荐!!
  • 今日算法:蓝桥杯基础题之“星期一”
  • CSS3 2D 转换
  • 不小心删除的照片怎么找回?三种方法恢复照片,千万别错过!
  • unity游戏开发——标记物体 一目了然
  • 数据结构与算法——二叉树(java)
  • 安装 Let‘s Encrypt certbot 生成多个域名免费 https 证书实录(linux pip 方式)
  • 97.SAP MII功能详解(11)Workbench-Transaction Logic(Assignment和Condition)
  • NFV架构
  • 20240902软考架构-------软考96-100答案解析
  • 【awk 】找到文件中数值最大的那一行,并输出该行的行号和内容