动态规划
相当于
dp[i][j]代表从i到j的最短路径
这时候的最短路径 仅仅是初始化的时候 点与点之间的直接相连。
每次向其中 增加一个中间节点
所有节点指点的dp状态都会随之改变,即每增加一个节点 所有的dp[i][j]都有可能更新
但是不论增加的节点的顺序是什么,最终的结果都是一样的
即增加 k1,k2 和 增加 k2,k1 最终结果一样
因此一共N个点 依次进行添加
其中 每次增加一个点都需要更新整个dp
所以是O(n^3)
我们从动态规划的角度考虑,解动态规划题目的重点就是合理的定义状态,划分阶段,我们定义 f[k][i][j] 为经过前k的节点,从i到j所能得到的最短路径,f[k][i][j]可以从f[k-1][i][j]转移过来,即不经过第k个节点,也可以从f[k-1][i][k]+f[k-1][k][j]转移过来,即经过第k个节点。
观察一下这个状态的定义,满足不满足最优子结构和无后效性原则。
最优子结构
图结构中一个显而易见的定理:
最短路径的子路径仍然是最短路径, 这个定理显而易见,比如一条从a到e的最短路a->b->c->d->e 那么 a->b->c 一定是a到c的最短路c->d->e一定是c到e的最短路,反过来,如果说一条最短路必须要经过点k,那么i->k的最短路加上k->j的最短路一定是i->j 经过k的最短路,因此,最优子结构可以保证。
无后效性
状态f[k][i][j]由f[k-1][i][j],f[k-1][i,k] 以及f[k-1][k][j]转移过来,很容易可以看出,f[k] 的状态完全由f[k-1]转移过来,只要我们把k放倒最外层循环中,就能保证无后效性。
满足以上两个要求,我们即证明了Floyd算法是正确的。
我们最后得出一个状态转移方程:f[k][i][j] = min(f[k-1][i][k],f[k-1][i][k],f[k-1][k][j]) ,可以观察到,这个三维的状态转移方程可以使用滚动数组进行优化。
K 为什么要放在最外层
采用动态规划思想,f[k][i][j] 表示 i 和 j 之间可以通过编号为 1…k 的节点的最短路径。
f[0][i][j] 初值为原图的邻接矩阵。
f[k][i][j]则可以从f[k-1][i][j]转移来,表示 i 到 j 不经过 k 这个节点。
也可以 f[k-1][i][k]+f[k-1][k][j] 从转移过来,表示经过 k 这个点。
意思即 f[k][i][j]=min(f[k-1][i][j], f[k-1][i][k]+f[k-1][k][j])
然后你就会发现 f 最外层一维空间可以省略,因为 f[k] 只与 f[k-1] 有关。
Floyd算法的本质是DP,而k是DP的阶段,因此要写最外面。
import java.util.Scanner; import java.util.*; public class Main{ public static void main(String[] args){ /*** Scanner in = new Scanner(System.in); int length = in.nextInt(); ArrayList<Integer> items = new ArrayList<Integer>(); while(in.hasNext()){ items.add(in.nextInt()); } items.sort(Comparator.naturalOrder()); Integer[] array = items.toArray(new Integer[0]); ***/ Scanner in = new Scanner(System.in); int N = in.nextInt(); int K = in.nextInt(); int M = in.nextInt(); int[][] map = new int[N][N]; for(int i=0;i<n;i++){ Arrays.fill(map[i],Integer.MAX_VALUE); } for(int i=0;i<n;i++){ map[i][i] = 0; } for(int i =0; i<M;i++){ int i = in.nextInt(); int j = in.nextInt(); int cost = in.nextInt(); map[i][j] = cost; } //---------------------------------------- int res = floyd(map,K); System.out.println(res); } public static int floyd(int[][] map, int K) { for (int k = 0; k < n; k++) { for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { if (map[i][k] != Integer.MAX_VALUE && map[k][j] != Integer.MAX_VALUE) { map[i][j] = Math.min(map[i][j], map[i][k] + map[k][j]); } } } } int max_ = Integer.MIN_VALUE; //最小路径中最大的就是最短完成时间。 for (int i = 1; i <= map.length; i++) { max_ = Math.max(max_, map[K][i]); } if (max_ == Integer.MAX_VALUE) return -1; return max_; } }