LeetCode 787. K 站中转内最便宜的航班(图/Bellman Ford算法)


贝尔曼-福特算法(Bellman-Ford)

简介

贝尔曼-福特算法(Bellman-Ford)是由理查德·贝尔曼(Richard Bellman) 和 莱斯特·福特 创立的,求解含负权边的带权有向图的单源最短路径问题的一种算法。它的原理是对图进行 V − 1 V-1 V1松弛操作 V V V代表图中的点数),从源点逐次绕过其他顶点,以缩短到达终点的最短路径长度,得到不超过 V − 1 V-1 V1条边构成的最短路径。其优于迪科斯彻算法(Dijkstra)的方面是边的权值可以为负数、实现简单,缺点是时间复杂度过高,高达 O ( V E ) O(VE) O(VE) E E E代表边数)。不能处理带负权边的无向图

Bellman-Ford算法的限制条件为:

  • 图中不能包含权值总和为负值的回路(负权值回路

Bell-Ford算法专用处理可能存在负环有限路线单源最短路问题。

算法思想

Bellman-Ford算法构造一个最短路径长度数组序列 d i s t 1 [ u ] , d i s t 2 [ u ] , ⋯ , d i s t n − 1 [ u ] dist^1[u],dist^2[u],\cdots,dist^{n-1}[u] dist1[u]dist2[u]distn1[u]。其中:

  • d i s t 1 [ u ] dist^1[u] dist1[u]为从源点 v v v到终点 u u u的只经过一条边的最短路径长度,并有 d i s t 1 [ u ] = e d g e [ v ] [ u ] dist^1[u]=edge[v][u] dist1[u]=edge[v][u]
  • d i s t 2 [ u ] dist^2[u] dist2[u]为从源点 v v v最多经过两条边到达终点 u u u的最短路径长度
  • d i s t 3 [ u ] dist^3[u] dist3[u]为从源点 v v v出发最多经过不构成负权值回路的三条边到达终点 u u u的最短路径长度
  • ⋯ \cdots
  • d i s t n − 1 [ u ] dist^{n-1}[u] distn1[u]为从源点 v v v出发最多经过不构成负权值回路的 n − 1 n-1 n1条边到达终点 u u u的最短路径长度

算法的最终目的是计算出 d i s t n − 1 [ u ] dist^{n-1}[u] distn1[u],为源点 v v v到顶点 u u u的最短路径长度

算法执行过程

在这里插入图片描述

a. 初始化操作,源点 s s s到自己的距离为 0 0 0,源点到其他点的距离为正 ∞ \infty

b. 第一次迭代,找到不超过1条边的最短路

c. 第二次迭代,找到不超过2条边的最短路

d. 第三次迭代,找到不超过3条边的最短路

e. 第四次迭代,找到不超过4条边的最短路

以此类推,可迭代次数是无数次的,但是如果不存在负环,那么当迭代次数与边数相等时就保证了结果

很重要的一点是每次迭代都是在上一次的基础上进行的,因此在代码实现时要注意保留上一次的结果,在上一次的基础上计算。

应用

题目描述

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

请你求出从 1 1 1号点到 n n n号点的最多经过 k k k条边的最短距离,如果无法从 1 1 1号点走到 n n n号点,输出 i m p o s s i b l e impossible impossible

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

输入格式

第一行包含三个整数 n , m , k n,m,k nmk

接下来 m m m行,每行包含三个整数 x , y , z x,y,z xyz,表示存在一条从点 x x x到点 y y y的有向边,边长为 z z z

输出格式

输出一个整数,表示从 1 1 1号点到 n n n号点的最多经过 k k k条边的最短距离。

如果不存在满足条件的路径,则输出“impossible”。

数据范围

1 ≤ n , k ≤ 500 1≤n,k≤500 1n,k500,

1 ≤ m ≤ 10000 1≤m≤10000 1m10000,

任意边长的绝对值不超过 10000 10000 10000

输入样例:

3 3 1

1 2 1

2 3 1

1 3 3

输出样例:

3

分析

因为存在可能存在负权边,所以Dijkstra算法无法完成。又因为可能存在负环,SPFA算法也无法完成。按道理来说,只要有负环且不限路径数,答案就可能是负无穷,Bellman-Ford算法也不能完成。但是本题给定了路径数k的限制,所以可以用Bellman-Ford算法来实现。

代码

import java.util.Arrays;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int m = sc.nextInt();
        int k = sc.nextInt();
        // 邻接矩阵
        int[][] g = new int[n][n];
        final int INF = Integer.MAX_VALUE / 2;
        // 初始化
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                g[i][j] = (i == j) ? 0 : INF;
            }
        }
        // 存图
        for (int i = 0; i < m; i++) {
            int x = sc.nextInt() - 1;
            int y = sc.nextInt() - 1;
            int z = sc.nextInt();
            g[x][y] = z;
        }
		// dist[x] = y 代表从「源点/起点」到 x 的最短距离为 y
        int[] dist = new int[n];
        // 初始化源点到自己的距离为0,源点到其他点的距离为正无穷
        Arrays.fill(dist, INF);
        dist[0] = 0;
        // 迭代k次
        for (int limt = 0; limt < k; limt++) {
            int[] clone = dist.clone();
            // 更新最短距离
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < n; j++) {
                    dist[j] = Math.min(dist[j], clone[i] + g[i][j]);
                }
            }
        }
        if (dist[n - 1] >= INF) {
            System.out.println("impossible");
        }
        System.out.println(dist[n - 1]);
    }
}
  • 时间复杂度为 O ( n m ) O(nm) O(nm)

LeetCode 787. K 站中转内最便宜的航班

题目描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

Bellman Ford + 邻接矩阵

本题中「限制最多经过不超过 k k k 个点」等价于「限制最多不超过 k + 1 k + 1 k+1 条边」,因此可以使用 Bellman Ford 来求解。

class Solution {
    public int findCheapestPrice(int n, int[][] flights, int src, int dst, int k) {
        final int INF = Integer.MAX_VALUE / 2;
        int[][] g = new int[n][n];
        // 邻接矩阵初始化
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                g[i][j] = i == j ? 0 : INF;
            }
        }
        for (int[] f : flights) {
            g[f[0]][f[1]] = f[2];
        }
		// dist[x] = y 代表从「源点/起点」到 x 的最短距离为 y
        int[] dist = new int[n];
        // 初始化源点到自己的距离为0,源点到其他点的距离为正无穷
        Arrays.fill(dist, INF);
        dist[src] = 0;
        // 迭代k + 1次
        for (int limit = 0; limit < k + 1; limit++) {
            // 复制上一次的迭代结果
            int[] clone = dist.clone();
            // 更新最短距离
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < n; j++) {
                    dist[j] = Math.min(dist[j], clone[i] + g[i][j]);
                }
            }
        }
        return dist[dst] >= INF ? -1 : dist[dst];
    }
}
  • 时间复杂度: O ( k ∗ n 2 ) O(k * n^2) O(kn2)
  • 空间复杂度: O ( n 2 ) O(n^2) O(n2)

Reference

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 好的,我来用中文回复这个链接:https://leetcode-cn.com/tag/dynamic-programming/ 这个链接是 LeetCode 上关于动态规划的目集合。动态规划是一种常用的算法思想,可以用来解决很多实际问,比如最长公共子序列、背包问、最短路径等等。在 LeetCode 上,动态规划也是一个非常重要的型,很多目都需要用到动态规划的思想来解决。 这个链接里包含了很多关于动态规划的目,按照难度从简单到困难排列。每个目都有详细的目描述、输入输出样例、目解析和代码实现等内容,非常适合想要学习动态规划算法的人来练习和提高自己的能力。 总之,这个链接是一个非常好的学习动态规划算法的资源,建议大家多多利用。 ### 回答2: 动态规划是一种算法思想,通常用于优化具有重叠子问和最优子结构性质的问。由于其成熟的数学理论和强大的实用效果,动态规划在计算机科学、数学、经济学、管理学等领域均有重要应用。 在计算机科学领域,动态规划常用于解决最优化问,如背包问像处理、语音识别、自然语言处理等。同时,在计算机网络和分布式系统中,动态规划也广泛应用于各种优化算法中,如链路优化、路由算法、网络流量控制等。 对于算法领域的程序员而言,动态规划是一种必要的技能和知识点。在LeetCode这样的程序员平台上,目分类和标签设置十分细致和方便,方便程序员查找并深入学习不同类型的算法LeetCode的动态规划标签下的目涵盖了各种难度级别和场景的问。从简单的斐波那契数列、迷宫问到可以用于实际应用的背包问、最长公共子序列等,难度不断递进且话丰富,有助于开发人员掌握动态规划的实际应用技能和抽象思维模式。 因此,深入LeetCode动态规划分类下的目学习和练习,对于程序员的职业发展和技能提升有着重要的意义。 ### 回答3: 动态规划是一种常见的算法思想,它通过将问拆分成子问的方式进行求解。在LeetCode中,动态规划标签涵盖了众多经典和优美的算法,例如斐波那契数列、矩阵链乘法、背包问等。 动态规划的核心思想是“记忆化搜索”,即将中间状态保存下来,避免重复计算。通常情况下,我们会使用一张二维表来记录状态转移过程中的中间值,例如动态规划求解斐波那契数列问时,就可以定义一个二维数组f[i][j],代表第i项斐波那契数列中,第j个元素的值。 在LeetCode中,动态规划标签下有众多难度不同的问。例如,经典的“爬楼梯”问,要求我们计算到n级楼梯的方案数。这个问的解法非常简单,只需要维护一个长度为n的数组,记录到达每一级楼梯的方案数即可。类似的问还有“零钱兑换”、“乘积最大子数组”、“通配符匹配”等,它们都采用了类似的动态规划思想,通过拆分问、保存中间状态来求解问。 需要注意的是,动态规划算法并不是万能的,它虽然可以处理众多经典问,但在某些场景下并不适用。例如,某些问的状态转移过程比较复杂,或者状态转移方程中存在多个参数,这些情况下使用动态规划算法可能会变得比较麻烦。此外,动态规划算法也存在一些常见误区,例如错用贪心思想、未考虑边界情况等。 总之,掌握动态规划算法对于LeetCode的学习和解都非常重要。除了刷以外,我们还可以通过阅读经典的动态规划书籍,例如《算法竞赛进阶指南》、《算法与数据结构基础》等,来深入理解这种算法思想。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xylitolz

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值