本文最后更新于 2025年1月2日 晚上
Bellman-Ford
时间复杂度$O(m\times n)$。
两点之间最短路最多包含所有点。如果一个点出现两次,就走了环,非负环不划算,负环没有最短路。
包含$k$条边的最短路可以由最多包含$k-1$条边的最短路加一条边来获得,这个操作是“松弛”。
如果某一个点的最短路被更新了,这个点就有能力更新它所连接的点。
当所有边不满足$d_u+w<d_v$时,最短路不会再改变了。每条边进行$n-1$遍“松弛”操作。
检查,如果有一条边还能松弛,则有负环。
1 2 3 4 5 6 7 8 9 10
| for (int i=1; i<=n; ++i) d[i]=INF; d[s]=0; for (int i=1; i<=n-1; ++i) { for (int j=1; j<=m; ++j) { u=e[j].u, v=e[j].v, w=e[j].w; if (d[i]<INF) d[v]=min(d[v], d[u]+w); } } for (int i=1; i<=m; ++i) if (d[e[i].u]+w<d[e[i].v]) ;
|
SPFA
全称Shortest Path Faster Algorithm,于1994年被西安交通大学段凡丁发明。
$n\le10^5$,可以有负权,可以判断负环。
时间复杂度$O(k\times m)$,$k$一般小于$2$。
如果一个点没有被改变,再判断能否“松弛”,是没有意义的。只有它被更新了才检查,用这个优化。
建立一个队列,初始点s,将它所更新的点压入队列,不更新的不管它。做完了弹出,取出下一个点继续更新。当这个队列空了,就意味着没有可以更新的点了,结束。
一个点可能多次进入队列,只要被更新就进入队列。每个顶点进入队列不会超过$n$次,如果超过$n$次就意味着存在负环。
手写队列长度为$10\times n$,也可以用长度为$n+1$的循环队列,也可以用STL::queue
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| int spfa(int s) { queue<int> q; memset(cnt, 0, sizeof(cnt)); memset(inq, 0, sizeof(inq)); for (int i=1; i<=n; ++i) d[i]=INF; d[s]=0; cnt[s]=1; q.push(s); inq[s]=1; while (!q.empty()) { int u=q.front(); q.pop(); inq[u]=0; for (int i=g[u]; i>0; i=e[i].next) { int v=e[i].v, w=e[i].w; if (d[u]+w<d[v]) { d[v]=d[u]+w; if (!inq[v]) { q.push(v); inq[v]=1; cnt[v]++; if (cnt[v]>n) return 1; } } } } return 0; }
|
SLF优化:
Small Label Fiest策略
如果一个点被更新地很短,前面的就会被浪费。
如果点$v$被更新,队首为$t$,且$d_v$<$d_t$,就把$v$放在队首。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| int spfa(int s) { deque<int> q; memset(cnt, 0, sizeof(s)); memset(inq, 0, sizeof(inq)); for (int i=1; i<=n; ++i) d[i]=INF; d[s]=0; cnt[s]=1; q.push_front(s); inq[s]=1; while (!q.empty()) { int u=q.front(); q.pop_front(); inq[u]=0; for (int i=g[u]; i>0; i=e[i].next) { int v=e[i].v, w=e[i].w; if (d[u]+w<d[v]) { d[v]=d[u]+w; if (!inq[v]) { if (!q.empty()&&d[v]<=d[q.front()]) q.push_front(v); else q.push_back(v); inq[v]=1; cnt[v]++; if (cnt[v]>n) return 1; } } } } return 0; }
|