0
点赞
收藏
分享

微信扫一扫

LeetCode 每周算法 6(图论、回溯)

写心之所想 2024-09-22 阅读 18

目录

一、Bellman_ford算法的应用

二、题目与题解

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

题目链接

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

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

题目链接

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

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

题目链接

题解: Bellman-Ford算法

 三、小结


一、Bellman_ford算法的应用

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

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

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

二、题目与题解

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

题目链接

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

题解:队列优化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)

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

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

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

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

 完整代码如下:

#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)

题解: Bellman-Ford算法

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

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

#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算法的打卡到此结束,后边会继续加油!

举报

相关推荐

0 条评论