-
多源最短路算法(Floyd)
说到最短路,就不得不提到松弛。
所谓松弛,就是当存在\(w_{u,v}>w_{u,k}+w_{k,v}\)时,令\(w_{u,v}=w_{u,k}+w_{k,v}\)
一般来说,松弛操作后,我们会用松弛后的边不断松弛其他边,而这,就是大部分最短路算法的思路。
思考一下,若用DP的思想考虑,那么我们易设出状态\(f_{i,j}\)表示从\(i\)到\(j\)的最短路,但是这个状态显然具有后效性,因为我们显然无法一遍就能转移出\(f_{i,j}\)的正确结果,需要不断对其他边进行松弛操作才能得出最后答案,所以同为\(f_{i,j}\)的值在不同阶段会有不同的值。
那么考虑加一维表示松弛阶段,则最显然表示松弛阶段的方式是边,然而边太多且一个边能松弛其他边的数量及其有限,故考虑点。
则设\(f_{k,i,j}\)为只考虑前\(k\)个点时,从\(i\)到\(j\)的最短路。
那么可以推出转移式\(f_{k,u,v} = min(f_{k,u,v},f_{k-1,u,k}+f_{k-1,k,v})\)
不断枚举\(k\),然后枚举所有\(i,j\),用\(k\)这个端点去更新它们。
观察到\(f_{k,i,j}\)的值只与\(f_{k-1,i,j}\)有关,故可以用滚动数组压成二维。
代码如下:
for (int k = 1; k <= n; ++k)
for (int u = 1; u <= n; ++u)
for (int v = 1; v <= n; ++v) f[u][v] = min(f[u][v], f[u][k] + f[k][v]); `
-
单源最短路算法(SPFA)
单源最短路只求从一个点出发到达其他点的最短路,故我们不需要把每条边都松弛一遍。
所以很自然地,我们可以想到从与出发点\(s\)相连的边开始,不断用已经知道的点来松弛其他边,直到再也无法松弛为止,这就是Bellman-Ford单源最短路算法的思路,但这显然很暴力,所以我们需要优化。
不难发现,我们似乎不需要那么多次松弛,我们只需要让每条被松弛的两点间路径再去松弛经过此两点间路径的路径就可以得出每条最短路。
所以考虑代码实现,设\(dis_v\)表示从起点\(s\)到\(v\)的最短路,我们只需要将起点\(s\)压入队列,对于队列中每个点\(u\)和其到达的下一个点\(v\)来说,如果出现了\(dis_v > dis_u + w_{u, v}\)的情况,那么就表示\(dis_v\)可以被松弛,并且与\(v\)有关的路径可能有\(dis_v\)路径的参与会更短,那么对\(dis_v\)进行松弛操作并把\(v\)压入队列进行处理,如果队列中没有点了,就说明最短路的搜索已经完成了。
值得注意的是,显然一个点大概率会被入队多次,所以为了尽可能地降低时间复杂度,我们可以用一个数组\(vis_u\)来表示点\(u\)是否在队列内,若是在的话就不用再次入队了。显然,当我们处理完一个点\(u\)时,需要使\(vis_u=0\)
这个算法被称作队列优化的Bellman-Ford算法,也被称作SPFA(Shortest Path Faster Algorithm)
代码如下:
void spfa(int st) {
q[++tl] = st, dis[st] = 0, vis[st] = 1;
while (hd <= tl) {
int u = q[hd++];
for (int i = head[u]; i; i = nxt[i]) {
int v = to[i];
if (dis[v] > dis[u] + w[i]) {
dis[v] = dis[u] + w[i];
if (!vis[v]) q[++tl] = v, vis[v] = 1;
}
}
vis[u] = 0;
}
}