算法导入
在上一篇博客中,咱讲述了求单源最短路径的一种经典算法 Dijkstra 算法,想了解的同学可以走前门瞅一瞅,记得回来哈。
经典Dijkstra算法 Java实现
但是因为算法的局限性,一是不能处理非负权图,二是只能处理单源到其他点的最短路径。有些小伙伴肯定不满意了呀!别急,今天咱介绍另一种的算法,Floyd算法,而且实现极其简单,for就完事了。先上图,咱梳理一下最短路中各种算法的差异。
算法核心
Floyd算法的核心思想为动态规划,动态规划是一种通过把原问题分解为相对简单的子问题的方式求解复杂问题。最简单的动态规划,斐波纳契数列,也叫兔子数列(兔子繁殖例子)1,1,2,3,5,8,13,21…。
对于上面的数列,我们可以首先确定边界条件是 F(0) = 1, F(1) = 1, 当 n >= 2时,F(n) = F(n - 1) + F(n-2)。当转移方程确定后,就能够方便的求出后者了。
那么在Floyd算法中,定义一个数组 f[k][x][y] 表示 只允许经过 结点1到 k,结点 x到 y的最短路径长度 ,也就是说,当 k = 6 时,可能 x - 1 - y 最短,也可能 x - 2 - 5 - 3 - y 最短,在满足条件下,f[k][x][y] = min(最短路径) 。假设有n个结点,编号1 - n,那么可以确定:
f[n][x][y] :结点 x 到 y 的最短路径。f[0][x][y] :不经过任何结点,x -> y 的最短路径。若两者直接相连,x -> y = w(x, y) 最短路径为权值,或者两者不直接相连,为正无穷 。
确定边界条件之后,我们来确定转移方程,对于任何一个f[k][x][y] , 有两种到达方式:
- 不经过 第
k 个结点,那么 f[k][x][y] = f[k - 1][x][y] ; - 经过 第
k 个结点了,那么 f[k][x][y] = f[k - 1][x][k] + f[k - 1][k][y] ; - 两者取最小值。
那么最终的转移方程即可确定 f[k][x][y] = min(f[k-1][x][y], f[k-1][x][k] + f[k-1][k][y] )
可以看到 如果顺序求值,第一维对结果并没有影响,那么可以转变为:f[x][y] = min(f[x][y], f[x][k] + f[k][y]) 。
转移方程有了,边界条件有了,那么代码就水到渠成了。
代码实现
import java.util.*;
public class Solution {
int[][] graph;
int INF = Integer.MAX_VALUE / 2;
public void floyd(int n, int[][] edges) {
graph = new int[n][n];
for (int i = 0; i < n; i++) {
Arrays.fill(graph[i], INF);
graph[i][i] = 0;
}
for(int[] e: edges) {
graph[e[0]][e[1]] = e[2];
graph[e[1]][e[0]] = e[2];
}
for (int k = 0; k < n; k++) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
graph[i][j] = Math.min(graph[i][j], graph[i][k] + graph[k][j]);
}
}
}
}
}
核心代码只有三个for循环,是不是很简单
没骗你们吧。但是要注意一点,由于时间复杂度为 O(n^3),而且空间复杂度 为 O(n ^ 2),当点较多时,那么就有可能造成 TLE 或 MLE ,做题目的时候尤其需要小心。
参考资料
OI Wiki 图灵程序设计丛书 算法 第4版
结尾
琅琅书声如春风,拂过千年时空 少年啊壮志在胸, 赋首词让人感动 许嵩 / 孙涛 《书香年华》
|