【Bellman-Ford算法(求解最短路径约束问题)】787. K 站中转内最便宜的航班——算法分析
有 n 个城市通过一些航班连接。给你一个数组 flights ,其中 flights[i] = [fromi, toi, pricei]
,表示该航班都从城市 fromi 开始,以价格 pricei 抵达 toi。现在给定所有的城市和航班,以及出发城市 src 和目的地 dst,你的任务是找到出一条最多经过 k 站中转的路线,使得从 src 到 dst
的 价格最便宜 ,并返回该价格。 如果不存在这样的路线,则输出 -1。
示例 1:
输入: n = 3, edges = [[0,1,100],[1,2,100],[0,2,500]] src = 0, dst = 2,
k = 1 输出: 200 解释: 城市航班图如下
从城市 0 到城市 2 在 1 站中转以内的最便宜价格是 200,如图中红色所示。
在学习的最短路径算法中,除了迪杰斯特拉算法、弗洛伊德算法之外,还有其他的算法,Bellman-Ford算法就是其中的一种,该算法解决了迪杰斯特拉算法中无法求解负权图的问题,同时对于最短路径中的约束问题也能够求解。
该算法实质上是动态规划算法,具体的算法过程如下:
给定图G(V, E)(其中V、E分别为图G的顶点集与边集),源点s,数组Distant[i]记录从源点s到顶点i的路径长度,初始化数组Distant[n]为无穷大,Distant[s]为0;
以下操作循环执行至多n-1次,n为顶点数: 对于每一条边e(u, v),如果Distant[u] + w(u, v) <Distant[v],则另Distant[v] = Distant[u]+w(u, v)。w(u, v)为边e(u,v)的权值;
若上述操作没有对Distant进行更新,说明最短路径已经查找完毕,或者部分点不可达,跳出循环。否则执行下次循环;
为了检测图中是否存在负环路,即权值之和小于0的环路。对于每一条边e(u, v),如果存在Distant[u] + w(u, v) <Distant[v]的边,则图中存在负环路,即是说改图无法求出单源最短路径。否则数组Distant[n]中记录的就是源点s到各顶点的最短路径长度。
可知,Bellman-Ford算法寻找单源最短路径的时间复杂度为O(V*E).
对于这道题目,我们可以使用该算法的思想:
我们用 f [ t ] [ i ] f[t][i] f[t][i] 表示通过恰好 tt 次航班,从出发城市 s r c src src 到达城市 i i i 需要的最小花费。在进行状态转移时,我们可以枚举最后一次航班的起点 j j j,即:
其中
(
j
,
i
)
∈
f
l
i
g
h
t
s
(j, i)∈flights
(j,i)∈flights 表示在给定的航班数组
f
l
i
g
h
t
s
flights
flights 中存在从城市
j
j
j 出发到达城市
i
i
i 的航班,
c
o
s
t
(
j
,
i
)
cost(j,i)
cost(j,i) 表示该航班的花费。该状态转移方程的意义在于,枚举最后一次航班的起点
j
j
j,那么前
t
−
1
t-1
t−1 次航班的最小花费为
f
[
t
−
1
]
[
j
]
f[t−1][j]
f[t−1][j] 加上最后一次航班的花费
c
o
s
t
(
j
,
i
)
cost(j,i)
cost(j,i) 中的最小值,即为
f
[
t
]
[
i
]
f[t][i]
f[t][i]。
由于我们最多只能中转 k k k 次,也就是最多搭乘 k + 1 k+1 k+1 次航班,最终的答案即为 f [ 1 ] [ d s t ] , f [ 2 ] [ d s t ] , ⋯ , f [ k + 1 ] [ d s t ] f[1][dst],f[2][dst],⋯,f[k+1][dst] f[1][dst],f[2][dst],⋯,f[k+1][dst]中的最小值。
【代码如下】
class Solution {
public:
int findCheapestPrice(int n, vector<vector<int>>& flights, int src, int dst, int k) {
// f[t][j] 表示经过t个中转(t+1条边)后到达j所需要的最小花费
vector<vector<int>> f(k+2,vector<int>(n,10000 * 101 + 1));
// 转移方程:f[t][j] = min(f[t-1][i]+cost(i,j))
f[0][src] = 0;
for(int t = 1;t<=k+1;t++){
for(auto&& flight:flights){
int i = flight[0];
int j = flight[1];
int cost = flight[2];
f[t][j] = min(f[t][j],f[t-1][i] + cost);
}
}
// 结果t = 0-k的f[t][j]最小值
int ans = 10000 * 101 + 1;
for(int t = 0;t<=k+1;t++){
ans = min(ans, f[t][dst]);
}
return ans == 10000 * 101 + 1?-1:ans;
}
};