【最短路径】Bellman–Ford 算法
Bellman-Ford 算法
贝尔曼-福特(Bellman–Ford)算法是一种基于松弛(relax)操作的最短路径算法,可以求出有负权的图的最短路径,并可以对最短路径不存在的情况进行判断。
记号
为了方便叙述,这里先给出下文将会用到的一些记号的含义。
-
为图上点的数目, 为图上边的数目; -
为最短路径的源点; -
为 点到 点的 实际 最短路径长度; -
为 点到 点的 估计 最短路径长度;任何时候都有:
,特别地,当最短路算法终止时,应有 。 -
为 这一条边的权重。
过程
先介绍 Bellman–Ford 算法要用到的松弛操作(Dijkstra 算法也会用到松弛操作)。
对于边
这么做的含义是显然的:我们尝试用
Bellman–Ford 算法所做的,就是不断尝试对图上每一条边进行松弛,我们每进行一轮循环,就对图上所有的边都尝试进行一次松弛操作,当一次循环中没有成功的松弛操作时,算法停止。
每次循环是
在最短路存在的情况下,由于一次松弛操作会使最短路径的边数至少
但还有一种情况,如果从
注意到前面的论证中已经说明了,对于最短路存在的图,松弛操作最多只会执行
举例
应用
应用1:Leetcode 787. K 站中转内最便宜的航班
题目
有
个城市通过一些航班连接。给你一个数组 ,其中 ,表示该航班都从城市 开始,以价格 抵达 。
现在给定所有的城市和航班,以及出发城市和目的地 ,你的任务是找到出一条最多经过 站中转的路线,使得从 到 的 价格最便宜 ,并返回该价格。 如果不存在这样的路线,则输出 。
示例 1:
输入:
,
, ,
输出: 200
解释:
城市航班图如下
从城市到城市 在 站中转以内的最便宜价格是 200,如图中红色所示。
分析
方法一:动态规划
假设
边界条件
当
- 如果
,即出发城市是 时,所需的花费为零; - 如果
,即出发的城市不是 时,所需的花费为 。
因此,边界条件:
状态转移
这里,我们用
因此,搭乘
由于我们最多只能中转
方法二:Bellman Ford 算法
题目可以等价转换为:在有向加权图上,经过最多不超过
注意:在遍历所有的“点对/边”进行松弛操作前,需要先对
代码实现
- 方法一
class Solution: def findCheapestPrice(self, n: int, flights: List[List[int]], src: int, dst: int, k: int) -> int: f = [[float("inf")] * n for _ in range(k + 2)] f[0][src] = 0 # 最多可以中转k次,即最多可搭乘航班k+1次 for time in range(1, k + 2): for start, end, cost in flights: f[time][end] = min(f[time][end], f[time - 1][start] + cost) result = min(f[t][dst] for t in range(1, k + 2)) return -1 if result == float("inf") else result
复杂度分析:
-
时间复杂度:
,其中,m 为边的条数。 -
空间复杂度:
-
方法二
class Solution { int INF = 0x3f3f3f3f; class Edge { int start; int end; int weight; Edge(int start, int end, int weight) { this.start = start; this.end = end; this.weight = weight; } } public int findCheapestPrice(int n, int[][] flights, int src, int dst, int k) { List<Edge> edges = new ArrayList<>(); for (int [] flight : flights) { edges.add(new Edge(flight[0], flight[1], flight[2])); } int [] distances = bellmanFord(src, n, edges, k); return distances[dst] == INF ? -1 : distances[dst]; } private int [] bellmanFord(int start, int n, List<Edge> edges, int k) { int [] distances = new int[n]; Arrays.fill(distances, INF); distances[start] = 0; for (int i = 0; i <= k; i++) { int [] currentDistance = distances.clone(); for (Edge edge : edges) { int u = edge.start; int v = edge.end; int w = edge.weight; distances[v] = Math.min(distances[v], currentDistance[u] + w); } } return distances; } }
复杂度分析:
-
时间复杂度:
,其中,m 为边的条数。 -
空间复杂度:
参考:
本文作者:LARRY1024
本文链接:https://www.cnblogs.com/larry1024/p/17514422.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步