2023-07-04 11:11阅读: 571评论: 0推荐: 0

【最短路径】Bellman–Ford 算法

Bellman-Ford 算法

贝尔曼-福特(Bellman–Ford)算法是一种基于松弛(relax)操作的最短路径算法,可以求出有负权的图的最短路径,并可以对最短路径不存在的情况进行判断。

记号

为了方便叙述,这里先给出下文将会用到的一些记号的含义。

  • n 为图上的数目,m 为图上的数目;

  • s 为最短路径的源点;

  • D(u)s 点到 u 点的 实际 最短路径长度;

  • distance(u)s 点到 u 点的 估计 最短路径长度;

    任何时候都有:distance(u)D(u),特别地,当最短路算法终止时,应有 distance(u)=D(u)

  • w(u,v)(u,v) 这一条边的权重

过程

先介绍 Bellman–Ford 算法要用到的松弛操作(Dijkstra 算法也会用到松弛操作)。

对于边 E(u,v),松弛操作对应下面的式子:

distance(v)=min(distance(v),distance(u)+w(u,v))

这么做的含义是显然的:我们尝试用 Suv(其中 Su 的路径取最短路径)这条路径去更新 v 点最短路径的长度,如果这条路径更优,就进行更新。

Bellman–Ford 算法所做的,就是不断尝试对图上每一条边进行松弛,我们每进行一轮循环,就对图上所有的边都尝试进行一次松弛操作,当一次循环中没有成功的松弛操作时,算法停止。

每次循环是 O(m) 的,那么最多会循环多少次呢?

在最短路存在的情况下,由于一次松弛操作会使最短路径的边数至少 +1,而最短路径的边数最多为 n1,因此整个算法最多执行 n1 轮松弛操作,因此,总时间复杂度为 O(nm)

但还有一种情况,如果从 S 点出发,抵达一个负环时,松弛操作会无休止地进行下去。

注意到前面的论证中已经说明了,对于最短路存在的图,松弛操作最多只会执行 n1 轮,因此如果第 n 轮循环时仍然存在能松弛的边,说明从 S 点出发,能够抵达一个负环。

举例

应用

应用1:Leetcode 787. K 站中转内最便宜的航班

题目

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

n 个城市通过一些航班连接。给你一个数组 flights ,其中 flights[i]=[fromi,toi,pricei] ,表示该航班都从城市 fromi 开始,以价格 pricei 抵达 toi
现在给定所有的城市和航班,以及出发城市 src 和目的地 dst,你的任务是找到出一条最多经过 k 站中转的路线,使得从 srcdst 的 价格最便宜 ,并返回该价格。 如果不存在这样的路线,则输出 1

示例 1:

输入:
n=3, edges=[[0,1,100],[1,2,100],[0,2,500]]
src=0, dst=2, k=1
输出: 200
解释:
城市航班图如下
image
从城市 0 到城市 21 站中转以内的最便宜价格是 200,如图中红色所示。

分析

方法一:动态规划

假设 dp[t][i] 表示从城市 src 出发,恰好搭乘 t 次航班,到达城市 i 的最小花费。

边界条件

t=0,即出发城市就是 src 时,显然有:

  • 如果 i=src ,即出发城市是 src 时,所需的花费为零;
  • 如果 isrc,即出发的城市不是 src 时,所需的花费为 INF

因此,边界条件:

dp[0][i]={0 ,i=src+ ,isrc

状态转移

这里,我们用 w(u,v) 表示 从城市 u 到城市 v 的花费,那么,我们可以枚举最后一次航班的起点 u,从而找到花费最少的线路。

因此,搭乘 t 次航班到达城市 v 的花费,可以通过 t1次航班的最小花费 d[t1][u] 加上 最后一次航班的花费 w(u,v),即:

dp[t][v]=min(u,v){dp[t1][u]+w(u,v)},(u,v)flights

由于我们最多只能中转 k 次,即最少搭乘 1 次航班,最多搭乘 k+1 次航班,因此,最终的答案 result 就是

result=mint=1k+1{dp[t][dst]}

方法二:Bellman Ford 算法

题目可以等价转换为:在有向加权图上,经过最多不超过 k+1 条边的最短路径,因此,可以使用 Bellman Ford 算法求解。

注意:在遍历所有的“点对/边”进行松弛操作前,需要先对 distance 进行备份,否则会出现 本次松弛操作所使用到的边,也是在同一次迭代所更新的,从而不满足边数限制的要求。

代码实现

  • 方法一
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

复杂度分析:

  • 时间复杂度:O(k×(n+n)),其中,m 为边的条数。

  • 空间复杂度:O(n×k)

  • 方法二

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;
}
}

复杂度分析:

  • 时间复杂度:O(k×(n+n)),其中,m 为边的条数。

  • 空间复杂度:O(n+m)


参考:

本文作者:LARRY1024

本文链接:https://www.cnblogs.com/larry1024/p/17514422.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   LARRY1024  阅读(571)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
  1. 1 404 not found REOL
404 not found - REOL
00:00 / 00:00
An audio error has occurred.