一、最短路径问题
1. 所有顶点对的最短路径
动态规划解法一:设 dp[i][j][m]
为顶点 i ~ j
中间最多经过 m
条边时的最短路径,不难得出存在下面状态转移方程 dp[i][j][m] = min{dp[i][k][m-1] + w[k][j]}
,其中 1<=i<=n
。假设原图不存在负权值回路,则对于包含 n
个点的图,任意顶点对 (i, j)
最短路径(简单路径)中间经过的边数最多为 n - 1
,因此我们的目标便是求解 dp[i][j][n-1]
。上述状态转移过程其实是可以用矩阵快速幂进行优化的,因此我们每次 dp[i][j][m-1]
向 dp[i][j][m]
状态进行松弛操作时,其本质是一种类矩阵乘法的操作,即 dp[i][j][m] = min{dp[i][1][m-1] + w(1, j), dp[i][2][m-1]+w(2, j), ..., dp[i][n][m-1]+w(n,j)}
(矩阵乘法 c[i][j] += a[i][k] * b[k][j], 1<=k<=n
)。有关矩阵快速幂的应用参考整理的矩阵乘法应用总结。
动态规划解法二(Floyd-Warshall):该算法按照顶点序号 1 ~ n
的顺序,不断将顶点加入到 (i,j)
的最短路径中,一步步进行最短路径的松弛计算。设 dp[i][j][k]
表示 i --> j
只经过 (1, 2, .., k)
顶点的最短路径,很容易得到下述状态转移方程 dp[i][j][k] = min(dp[i][j][k-1], dp[i][k][-1] + dp[k][j][k-1])
,于是我们要求解的最终状态变成 dp[i][j][n]
。另外,由上述状态转移方程可知,对于 dp[i][j][k]
状态的求解只取决于 dp[i][j][k-1]
,因此利用滚动数组
对整个状态转移过程进行空间优化,即状态转移方程变成dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j])
。同时,在求解最短路径的过程中,我们可以维护 (i,j)
的最短路径上j
的前驱节点,设 path[i][j]
,则 path[i][j]
的状态转移方程如下所示,path[i][j] = path[i][j] or path[k][j]
。最后,处理好边界条件,搞起来即可。
const (
inf = 0x3f3f3f3f
maxn = 110
)
var (
adj [maxn][maxn]int // 邻接矩阵
path [maxn][maxn]int // (i,j)最短路径中j的前驱节点
dp [maxn][maxn]int // (i,j)最短路径取值
)
// 顶点编号 1 ~ n
func solve(n int) {
for i := 1; i <= n; i++ {
for j := 1; j <= n; j++ {
dp[i][j] = adj[i][j]
if adj[i][j] >= inf {
path[i][j] = -1
} else {
path[i][j] = i
}
}
}
for k := 1; k <= n; k++ {
for i := 1; i <= n; i++ {
for j := 1; j <= n; j++ {
if dp[i][j] > dp[i][k]+dp[k][j] {
dp[i][j] = dp[i][k] + dp[k][j]
path[i][j] = path[k][j]
}
}
}
}
}
Floyd-Warshall 算法扩展
该算法为利用动态规划求解图中任意顶点对(i,j)
路径上的一些性质,不应该仅仅局限于最短路径这个性质。例如,利用该状态转移过程,同样可以求解:
-
有向图传递闭包:
dp[i][j] |= (dp[i][k]|dp[k][j])
-
(i,j)
路径上最长边最小化:dp[i][j] = min(dp[i][j], max(dp[i][k], dp[k][j]))
稀疏图最短路径(Johnson)
简单说下该算法的核心几点:
SPFA
判断负权值回路- 单源最短路径求解:Dijkstra + Fibonacci Heap
- 寻找新的权重函数
w'
使得w'(i,j)
非负且满足新权重函数w'
下求解的(i,j)
最短路径在旧权重函数w
下仍然为最短路径。由于边权值非负,在稀疏图的情况下便可以用Dijkstra+Heap
优化最短路径的求解了。 - 新权重函数构造:
w'(i,j) = w(i,j)+h(i)-h(j)
,其中h(i)
表示添加新源点后源点到i
点的最短路径,由于最短路径满足三角不等式h(j) <= h(i) + w(i,j)
,所以w(i,j)
非负。w(s,vi) = 0
,以s
为源点求解单源最短路径。
算法导论中关于该算法的流程如下所示:
TODO: 晚上回家学习 Markdown 公式编辑,以后尽量用公式编辑的方式。