【代码随想录训练营第42期 Day60打卡 - 图论Part10 - Bellman_ford算法系列运用

news2024/9/21 4:36:05

目录

一、Bellman_ford算法的应用

二、题目与题解

题目一:卡码网 94. 城市间货物运输 I

题目链接

题解:队列优化Bellman-Ford算法(SPFA)

题目二:卡码网 95. 城市间货物运输 II

题目链接

题解: 队列优化Bellman-Ford算法(SPFA)

题目三:卡码网 96. 城市间货物运输 III

题目链接

题解: Bellman-Ford算法

 三、小结


一、Bellman_ford算法的应用

Bellman-Ford算法是一种用于解决单源最短路径问题的算法,它能够处理含有负权边的图,并且能够检测图中是否存在负权回路

其应用一般分为以下几个方面:

1、最短路径问题:在图论中,Bellman - Ford算法是解决单源最短路径问题的有效工具。它可以找到从一个顶点到所有其他顶点的最短路径。

2、负权边:与Dijkstra算法不同,Bellman - Ford算法能够处理含有负权边的图。这意味着它可以解决那些包含负权重边的图的最短路径问题。

3、运输问题:在运输问题中,需要找到从一个供应点(源)到多个需求点(汇)的最小成本运输路径。Bellman - Ford算法可以用来解决这个问题,尤其是在存在负权边的情况下。

4、流网络问题:在流网络中,Bellman - Ford算法可以用来找到从一个节点到另一个节点的最大流。这是因为在某些流网络问题中,边的权重可以表示为流量的限制。

5、网络流问题:在网络流问题中,Bellman - Ford算法可以用来找到从一个源点到汇点的最小费用流。这涉及到在网络中传输物质或信息,并需要最小化成本。

6、动态最短路径问题:在某些情况下,图的结构可能会发生变化,例如添加或删除边。Bellman - Ford算法可以用来动态地更新最短路径信息。

7、多源最短路径问题:Bellman - Ford算法可以扩展为解决多源最短路径问题,即找到多个源点到其他所有顶点的最短路径。

8、网络路由问题:在网络路由问题中,需要找到从一个网络节点到另一个网络节点的最佳路径。Bellman - Ford算法可以用来解决包含负权边的网络路由问题。

Bellman-Ford算法的主要优点是它能够处理负权边,这是其他最短路径算法(如Dijkstra算法)所不能做到的。然而,它的主要缺点是时间复杂度较高,为O(VE),其中V是顶点数,E是边数。在实际应用中,如果图中的边数远大于顶点数,Bellman-Ford算法可能不如Dijkstra算法高效。 

二、题目与题解

题目一:卡码网 94. 城市间货物运输 I

题目链接

94. 城市间货物运输 I (kamacoder.com)

题目描述

某国为促进城市间经济交流,决定对货物运输提供补贴。共有 n 个编号为 1 到 n 的城市,通过道路网络连接,网络中的道路仅允许从某个城市单向通行到另一个城市,不能反向通行。

网络中的道路都有各自的运输成本和政府补贴,道路的权值计算方式为:运输成本 - 政府补贴。权值为正表示扣除了政府补贴后运输货物仍需支付的费用;权值为负则表示政府的补贴超过了支出的运输成本,实际表现为运输过程中还能赚取一定的收益。

请找出从城市 1 到城市 n 的所有可能路径中,综合政府补贴后的最低运输成本。如果最低运输成本是一个负数,它表示在遵循最优路径的情况下,运输过程中反而能够实现盈利。

城市 1 到城市 n 之间可能会出现没有路径的情况,同时保证道路网络中不存在任何负权回路。

输入描述

第一行包含两个正整数,第一个正整数 n 表示该国一共有 n 个城市,第二个整数 m 表示这些城市中共有 m 条道路。 

接下来为 m 行,每行包括三个整数,s、t 和 v,表示 s 号城市运输货物到达 t 号城市,道路权值为 v (单向图)。

输出描述

如果能够从城市 1 到连通到城市 n, 请输出一个整数,表示运输成本。如果该整数是负数,则表示实现了盈利。如果从城市 1 没有路径可达城市 n,请输出 "unconnected"。

输入示例

6 7
5 6 -2
1 2 1
5 3 1
2 5 2
2 4 -3
4 6 4
1 3 5

输出示例

1

提示信息

示例中最佳路径是从 1 -> 2 -> 5 -> 6,路上的权值分别为 1 2 -2,最终的最低运输成本为 1 + 2 + (-2) = 1。

示例 2:

4 2
1 2 -1
3 4 -1

在此示例中,无法找到一条路径从 1 通往 4,所以此时应该输出 "unconnected"。

数据范围:

1 <= n <= 1000;
1 <= m <= 10000;

-100 <= v <= 100;

题解:队列优化Bellman-Ford算法(SPFA)

这题在昨天的打卡中已经有提到一般实现的Bellman-Ford算法,今天这里将用队列优化后的Bellman-Ford算法进行实现。

Bellman - Ford算法实现

1、创建一个队列q,先将源点加入队列。

2、进入循环,当队列非空时,继续执行以下操作:

         (1)从队列中取出队头节点node;

         (2)标记该节点已从队列中取出;

         (3)遍历当前节点的所有邻接边:

                 a.如果通过当前节点到达邻接节点的距离更短,则更新最短距离;

                 b.如果邻接节点不在队列中,则将其加入队列,并标记为已加入。

完整代码如下:

#include <bits/stdc++.h>
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); // 创建一个邻接表,存储图的信息,大小为n+1,因为节点编号从1开始

    vector<bool> inQueue(n + 1); // 用于标记节点是否已经在队列中(避免重复添加)

    // 将所有边保存起来,构建邻接表
    for (int i = 0; i < m; i++)
    {
        cin >> p1 >> p2 >> val;
        grid[p1].push_back(Edge(p2, val)); // p1指向p2,边权重为val,将边添加到邻接表中
    }
    int start = 1; // 起点
    int end = n;   // 终点

    vector<int> minDist(n + 1, INT_MAX); // 初始化最短距离数组,所有节点到源点的最短距离初始为无穷大
    minDist[start] = 0;                  // (除外)源点到自己的距离为0

    // 队列优化Bellman-Ford算法
    queue<int> q;
    q.push(start);  //先将起点加入队列

    while (!q.empty())
    {
        int node = q.front(); // 取出队头节点 -- 作为后续邻接边的起始节点
        q.pop();
        inQueue[node] = false; // 标记该节点已从队列中取出
        // 遍历当前节点的所有邻接边 -- 当前节点即是这些边的起始节点
        for (Edge edge : grid[node])
        {
            int from = node;
            int to = edge.to;
            int value = edge.val;
            if (minDist[to] > minDist[from] + value) // 开始松弛:如果通过当前节点到达邻接节点to的距离更短,则更新邻接节点to到源点的最短距离
            { 
                minDist[to] = minDist[from] + value;
                if (inQueue[to] == false) // 如果该节点不在队列中,则加入队列,并标记为已加入
                { 
                    q.push(to);
                    inQueue[to] = true;
                }
            }
        }
    }
    if (minDist[end] == INT_MAX)
        cout << "unconnected" << endl; // 不能到达终点
    else
        cout << minDist[end] << endl; // 到达终点最短路径
}

题目二:卡码网 95. 城市间货物运输 II

题目链接

95. 城市间货物运输 II (kamacoder.com)

题目描述

某国为促进城市间经济交流,决定对货物运输提供补贴。共有 n 个编号为 1 到 n 的城市,通过道路网络连接,网络中的道路仅允许从某个城市单向通行到另一个城市,不能反向通行。

网络中的道路都有各自的运输成本和政府补贴,道路的权值计算方式为:运输成本 - 政府补贴。权值为正表示扣除了政府补贴后运输货物仍需支付的费用;权值为负则表示政府的补贴超过了支出的运输成本,实际表现为运输过程中还能赚取一定的收益。

然而,在评估从城市 1 到城市 n 的所有可能路径中综合政府补贴后的最低运输成本时,存在一种情况:图中可能出现负权回路。负权回路是指一系列道路的总权值为负,这样的回路使得通过反复经过回路中的道路,理论上可以无限地减少总成本或无限地增加总收益。为了避免货物运输商采用负权回路这种情况无限的赚取政府补贴,算法还需检测这种特殊情况。

请找出从城市 1 到城市 n 的所有可能路径中,综合政府补贴后的最低运输成本。同时能够检测并适当处理负权回路的存在。

城市 1 到城市 n 之间可能会出现没有路径的情况

输入描述

第一行包含两个正整数,第一个正整数 n 表示该国一共有 n 个城市,第二个整数 m 表示这些城市中共有 m 条道路。 

接下来为 m 行,每行包括三个整数,s、t 和 v,表示 s 号城市运输货物到达 t 号城市,道路权值为 v。

输出描述

如果没有发现负权回路,则输出一个整数,表示从城市 1 到城市 n 的最低运输成本(包括政府补贴)。如果该整数是负数,则表示实现了盈利。如果发现了负权回路的存在,则输出 "circle"。如果从城市 1 无法到达城市 n,则输出 "unconnected"。

输入示例

4 4
1 2 -1
2 3 1
3 1 -1 
3 4 1

输出示例

circle

提示信息

路径中存在负权回路,从 1 -> 2 -> 3 -> 1,总权值为 -1,理论上货物运输商可以在该回路无限循环赚取政府补贴,所以输出 "circle" 表示已经检测出了该种情况。

数据范围:

1 <= n <= 1000;
1 <= m <= 10000;

-100 <= v <= 100;

题解: 队列优化Bellman-Ford算法(SPFA)

这题是bellman-ford算法判断负权回路的应用

和上一题相比较,区别也就在于对负权回路的判断,其余思路保持一致。

故这里有一个关键,即如何判断负权回路:

我们用计数器记录每个节点加入队列的次数。如果某个节点的计数器达到了n(即所有节点的数量),那么这个节点在经过V-1次松弛操作后,仍然可以通过负权边继续进行松弛。这违反了Bellman-Ford算法的假设,即在V-1次松弛操作后,最短路径应该已经被找到。因此,可以判断出图中存在负权回路。

 完整代码如下:

#include <bits/stdc++.h>
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); // 创建一个邻接表,存储图的信息,大小为n+1,因为节点编号从1开始

    vector<bool> inQueue(n + 1); // 用于标记节点是否已经在队列中(避免重复添加)

    // 将所有边保存起来,构建邻接表
    for (int i = 0; i < m; i++)
    {
        cin >> p1 >> p2 >> val;
        grid[p1].push_back(Edge(p2, val)); // p1指向p2,边权重为val,将边添加到邻接表中
    }
    int start = 1; // 起点(源点)
    int end = n;   // 终点

    vector<int> minDist(n + 1, INT_MAX);
    minDist[start] = 0;

    queue<int> q;
    q.push(start); // 队列里放入起点

    vector<int> count(n + 1, 0); // 创建一个计数器数组,用于记录每个节点加入队列的次数
    count[start]++;              // 刚放入一次起点,计数+1

    bool flag = false; // 设置一个标志,用于标记是否找到了负权回路,初始化为false
    while (!q.empty())
    {
        int node = q.front();
        q.pop();
        for (Edge edge : grid[node])
        {
            int from = node;
            int to = edge.to;
            int value = edge.val;
            if (minDist[to] > minDist[from] + value) // 开始松弛:如果通过当前节点到达邻接节点to的距离更短,则更新邻接节点to到源点的最短距
            {
                minDist[to] = minDist[from] + value;
                q.push(to);
                count[to]++;
                if (count[to] == n) // 关键:如果加入队列次数超过n-1次,就说明该图与负权回路
                {
                    flag = true;
                    while (!q.empty())
                        q.pop();
                    break;
                }
            }
        }
    }

    if (flag) // 如果存在负权回路,输出"circle"
        cout << "circle" << endl;
    else if (minDist[end] == INT_MAX)
        cout << "unconnected" << endl;
    else
        cout << minDist[end] << endl;
}

题目三:卡码网 96. 城市间货物运输 III

题目链接

96. 城市间货物运输 III (kamacoder.com)

题目描述

某国为促进城市间经济交流,决定对货物运输提供补贴。共有 n 个编号为 1 到 n 的城市,通过道路网络连接,网络中的道路仅允许从某个城市单向通行到另一个城市,不能反向通行。

网络中的道路都有各自的运输成本和政府补贴,道路的权值计算方式为:运输成本 - 政府补贴。权值为正表示扣除了政府补贴后运输货物仍需支付的费用;权值为负则表示政府的补贴超过了支出的运输成本,实际表现为运输过程中还能赚取一定的收益。

请计算在最多经过 k 个城市的条件下,从城市 src 到城市 dst 的最低运输成本。

输入描述

第一行包含两个正整数,第一个正整数 n 表示该国一共有 n 个城市,第二个整数 m 表示这些城市中共有 m 条道路。

接下来为 m 行,每行包括三个整数,s、t 和 v,表示 s 号城市运输货物到达 t 号城市,道路权值为 v。

最后一行包含三个正整数,src、dst、和 k,src 和 dst 为城市编号,从 src 到 dst 经过的城市数量限制。

输出描述

输出一个整数,表示从城市 src 到城市 dst 的最低运输成本,如果无法在给定经过城市数量限制下找到从 src 到 dst 的路径,则输出 "unreachable",表示不存在符合条件的运输方案。

输入示例

6 7
1 2 1
2 4 -3
2 5 2
1 3 5
3 5 1
4 6 4
5 6 -2
2 6 1

输出示例

0

提示信息

从 2 -> 5 -> 6 中转一站,运输成本为 0。 

1 <= n <= 1000; 

1 <= m <= 10000; 

-100 <= v <= 100;

题解: Bellman-Ford算法

这题是bellman_ford算法解决单源有限最短路问题的应用。

这题Bellman - Ford算法实现的关键在于:

使用minDist_copy来保留上一次迭代的结果,从而避免重复计算和比较,提高算法的效率。如果在当前迭代中没有发现新的更短路径,那么在接下来的迭代中,可以只检查minDist_copy是否已经包含了一条更短的路径,如果没有,那么就不需要更新minDist数组。

通过进行k + 1次松弛操作,每次迭代开始时,将当前的minDist数组的内容复制到minDist_copy中。 遍历所有边,进行松弛操作。如果在松弛过程中发现通过某个节点到达某个邻接节点的距离更短,则更新最短距离。

#include <bits/stdc++.h>
using namespace std;

int main()
{
    int src, dst, k, p1, p2, val, m, n; // 起点src,终点dst,松弛次数k
    cin >> n >> m;

    vector<vector<int>> grid; // 创建一个二维向量grid,用于存储图的信息:每个元素都是一条边(包含起始点,终止点,权值)

    // 读取所有边,并添加到grid中
    for (int i = 0; i < m; i++)
    {
        cin >> p1 >> p2 >> val;
        grid.push_back({p1, p2, val});
    }

    cin >> src >> dst >> k;

    vector<int> minDist(n + 1, INT_MAX); // 用于存储从起点到每个节点的最短距离,都初始化为最大值INT_MAX
    minDist[src] = 0;                    // 起点除外,起点到本身的距离为0
    vector<int> minDist_copy(n + 1);     // 用来记录上一次遍历的结果

    // 进行k次松弛操作
    for (int i = 1; i <= k + 1; i++)
    {
        minDist_copy = minDist; // 将上一次计算的结果赋值给minDist_copy:即将当前的minDist数组的内容复制到一个新的数组minDist_copy中
        // 遍历所有边,进行松弛操作
        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;
            }
        }
    }
    if (minDist[dst] == INT_MAX) // 不能到达终点
        cout << "unreachable" << endl;
    else
        cout << minDist[dst] << endl; // 到达终点最短路径
}

 三、小结

Bellman_ford算法的打卡到此结束,后边会继续加油!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2144897.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MySQL高阶1783-大满贯数量

题目 找出每一个球员赢得大满贯比赛的次数。结果不包含没有赢得比赛的球员的ID 。 结果集 无顺序要求 。 准备数据 Create table If Not Exists Players (player_id int, player_name varchar(20)); Create table If Not Exists Championships (year int, Wimbledon int, F…

Unity 高亮插件HighlightPlus介绍

仅对官方文档进行了翻译 注意:官方文档本身就落后实际,但对入门仍很有帮助,核心并没有较大改变,有的功能有差异,以实际为准.(目前我已校正了大部分差异,后续我会继续维护该文档) 为什么为该插件做翻译?功能强大,使用简单,且还在维护. 基于此版本的内置渲染管线文档 快速开始…

C语言之预处理详解(完结撒花)

目录 前言 一、预定义符号 二、#define 定义常量 三、#define定义宏 四、宏与函数的对比 五、#和## 运算符 六、命名约定 七、#undef 八、条件编译 九、头文件的包含 总结 前言 本文为我的C语言系列的最后一篇文章&#xff0c;主要讲述了#define定义和宏、#和##运算符、各种条件…

9.18作业

提示并输入一个字符串&#xff0c;统计该字符串中字母、数字、空格、其他字符的个数并输出 代码展示 #include <iostream>using namespace std;int main() {string str;int countc 0; // 字母计数int countn 0; // 数字计数int count 0; // 空格计数int counto 0;…

IEEE-754 32位十六进制数 转换为十进制浮点数

要将 IEEE-754 32位十六进制数 转换为 十进制浮点数&#xff0c;可以使用LabVIEW中的 Type Cast 函数。以下是一些具体步骤&#xff0c;以及相关实例的整理&#xff1a; 实现步骤&#xff1a; 输入十六进制数&#xff1a;在LabVIEW中&#xff0c;首先需要创建一个输入控制器&am…

2024最新软件测试面试题【1000道题含答案】

1、自动化代码中,用到了哪些设计模式? 单例设计模式 工厂模式PO设计模式数据驱动模式面向接口编程设计模式 2、什么是断言( Assert) ? 断言Assert用于在代码中验证实际结果是不是符合预期结果&#xff0c;如果测试用例执行失败会抛出异常并提供断言日志 3、什么是web自动化…

C++之继承(通俗易懂版)

前言&#xff1a;我们都知道C是一门支持过程化编程&#xff0c;面向对象的高级语言&#xff0c;既然是面向对象的语言&#xff0c;那么对于对象而言&#xff0c;对象会有很多中相同的属性&#xff0c;举个例子:你和你老师&#xff0c;你们都有着共同的属性和身份&#xff0c;例…

Longman Dictionary of Contemporary English (朗文当代高级英语辞典)

Longman Dictionary of Contemporary English {朗文当代高级英语辞典} 1. Longman Dictionary of Contemporary English1.1. school References 1. Longman Dictionary of Contemporary English https://www.ldoceonline.com/ 1.1. school https://www.ldoceonline.com/dicti…

C++: 高效使用智能指针的8个建议

前言&#xff1a;智能指针是C11的新特性&#xff0c;它基于RAII实现&#xff0c;可以自动管理内存资源&#xff0c;避免内存泄漏的发生&#xff0c;但是智能指针也并不是万能的&#xff0c;如果不正确使用智能指针&#xff0c;也会导致内存泄漏的发生&#xff0c;因此&#xff…

嵌入式人工智能项目及人工智能应用项目——大合集列表查阅

本文的项目合集列表可能更新不及时&#xff08;会及时更新&#xff09;&#xff0c;可查阅实时更新的链接如下。 嵌入式人工智能及人工智能应用项目合集实时更新链接如下&#xff1a; 阿齐嵌入式人工智能及人工智能应用项目合集 (kdocs.cn)https://www.kdocs.cn/l/cc97tuieys4…

MacOS安装MAT教程

MAT下载地址MAT下载地址MAT下载地址MAT下载地址 如果不知道你的芯片类型, 可以执行如下命令 uname -m

Fiddler抓包工具实战

文章目录 &#x1f7e2; Fiddler入门到精通&#x1f449;主要功能&#x1f449;使用场景 &#x1f7e2; 一、Fiddler抓包和F12抓包对比&#x1f7e2; 二、Fiddler的核心功能&#x1f7e2; 三、Fiddler的工作原理&#x1f7e2; 四、Fiddler功能配置使用&#x1f449;规则设置&am…

力扣 11题 盛最多水的容器

前言&#xff1a;这道题其实挺有意思的&#xff0c;前段时间在刷视频的时候看都了很多博主都在讲解这道题&#xff0c;最近在写一些算法的东西&#xff0c;我就顺势刷到了这道题。在这里写一写我自己的对这道题的理解。 题目详见&#xff1a;https://leetcode.cn/problems/con…

【DAY20240918】03教你轻松配置 Git 远程仓库并高效推送代码!

文章目录 前言 git diff一、远程仓库&#xff1f;1、在 Gitee 上新建仓库&#xff1a;2、Git 全局设置&#xff1a;3、添加远程仓库&#xff1a;4、推送本地代码到远程仓库&#xff1a;5、输入用户名和密码&#xff1a;6、后续推送&#xff1a; 二、全情回顾三、参考 前言 git …

谷歌浏览器扩展程序怎么提升CSS开发效率

在现代Web开发中&#xff0c;CSS&#xff08;层叠样式表&#xff09;是不可或缺的一部分&#xff0c;它负责网页的视觉呈现和布局设计。为了提高CSS开发的效率&#xff0c;谷歌浏览器提供了许多实用的扩展程序。本文将介绍几个关键的扩展程序&#xff0c;并探讨如何利用它们来优…

网络安全学习(五)Burpsuite实战

bp功能确实强大&#xff0c;记录一个bp手机验证码的实例。 当然&#xff0c;首先要打开bp&#xff0c;设置好浏览器的代理。 浏览器访问实例网址www.xxx.com&#xff08;隐藏真实网址&#xff09;。 真实网址有个注册功能&#xff0c;需要手机验证码。 好的&#xff0c;我们…

深入探索Android开发之Java核心技术学习大全

Android作为全球最流行的移动操作系统之一&#xff0c;其开发技能的需求日益增长。本文将为您介绍一套专为Android开发者设计的Java核心技术学习资料&#xff0c;包括详细的学习大纲、PDF文档、源代码以及配套视频教程&#xff0c;帮助您从Java基础到高级特性&#xff0c;再到A…

『功能项目』回调函数处理死亡【54】

我们打开上一篇53伤害数字UI显示的项目&#xff0c; 本章要做的事情是使用回调函数处理怪物Boss01死亡后增加主角经验值的功能&#xff0c;以及生成一个七秒的升级特效 首先增加一个技能特效重命名为PlayerUpGradeEffect 修改脚本&#xff1a;BossCtrl.cs 修改脚本&#xff1a…

SpringBoot2:web开发常用功能实现及原理解析-@ControllerAdvice实现全局异常统一处理

文章目录 前言1、工程包结构2、POM依赖3、Java代码 前言 本篇主要针对前后端分离的项目&#xff0c;做的一个统一响应包装、统一异常捕获处理。 在Spring里&#xff0c;我们可以使用ControllerAdvice来声明一些关于controller的全局性的东西&#xff0c;其用法主要有以下三点…

PostgreSQL技术内幕11:PostgreSQL事务原理解析-MVCC

文章目录 0.简介1.MVCC介绍2.MVCC常见的实现方式3.PG的MVCC实现3.1 可见性判断3.2 提交/取消 0.简介 本文主要介绍在事务模块中MVCC(多版本并发控制&#xff09;常见的实现方式&#xff0c;优缺点以及PG事务模块中MVCC&#xff08;多版本并发控制&#xff09;的实现。 1.MVCC…