图论-SPFA
就像在我上一篇文章中提到的那样,BellmanFord算法有很多不必要的松弛操作,对于某一个最短路来说 ( v 0 , v 1 , … , v k ) (v_{0},v_{1},\dots,v_{k}) (v0,v1,…,vk),从 v 0 v_{0} v0所连的边开始松弛才是最高效的,因此我们有如下定理:
只有那些刚被松弛的边所连接的节点,才会引起这个节点所连的边松弛。
我们根据这一条定理,给出了BellmanFord算法的改进算法,即SPFA(Shortest Path Faster Algorithm)。
void bellmanFord(int n, int s, vector<vector<pair<int, int>>> &adj)
{
for (int i = 0; i < n; i++)
{
d[i] = INT32_MAX;
}
d[s] = 0;
queue<int> que;
set<int> in_que;
que.push(s);
in_que.insert(s);
while (!que.empty())
{
int u = que.front();
que.pop();
in_que.erase(u);
vector<pair<int, int>> &nxt = adj[u];
for (pair<int, int> v : nxt)
{
if (relax(u, v.first, v.second) && !in_que.count(v.first))
{
que.push(v.first);
in_que.insert(v.first);
}
}
}
}
初始化和传统bellmanford算法相同,不同的是,我们通过一个队列维护这个可能引起边松弛的节点。初始化先将源节点加入队列。我们通过循环不变式证明循环的正确性。
- 断言:队列里面的节点都是可能引起松弛操作的节点。
- 初始化:一开始只有源节点能引起松弛操作。
- 保持:我们取出一个节点,去松弛他的邻接边,如果遇到了松弛成功的边,那么和边相连的节点v就会引起下一次的松弛,如果v不在队列里面,那么就可以把v加入队列里面,循环结束后,队列里面的节点都是可能引起松弛操作的节点。
- 终止:每一个节点都更新完毕,没有会引起松弛的点和边,队列为空,循环终止。
上述我们是用队列实现的SPFA,我们也可以换成栈,堆,等其他数据结构。渐进时间复杂度仍为VE,最坏情况将退化为传统BellFord算法(不计数据结构操作时间)。
更多优化请看: