算法 | SPFA (最短路径快速算法)
解决赋权图的单源最短路径问题:SPFA (Shortest Path Faster Algorithm, 最短路径快速算法) - Bellman-Ford 的队列优化算法
- 能解决负边
- 能解决负环
SPFA
复杂度
平均时间复杂度:$O(\left|E\right|)$
最差时间复杂度:$O(\left|V\right|\cdot\left|E\right|)$
($\left|V\right|$ 为顶点数,$\left|E\right|$ 为边数)
分析
SPFA 算法为 Bellman-Ford 算法的优化,所以建议先学习 Bellman-Ford 算法:https://io.zouht.com/64.html
在 Bellman-Ford 算法中,每一次迭代我们一股脑地对所有边进行了松弛操作,但实际上很多松弛操作是没有意义的。如果一个点距离没有更新,那么没有必要对它进行松弛操作,因为答案根本不会变化。SPFA 就是针对这一点进行了优化。
SPFA 使用一个队列,若一个点被更新了,那么就将其加入队列,等待松弛操作。简单来说就是只有距离被更新过的点才进行松弛操作,若一个点没有变化,不会对其进行操作。
代码实现
const int MAXN = 1e5 + 10, INF = 0x3f3f3f3f; // INF代表无穷大
int e, v, s; // e边数 v顶点数 s起点
vector<pair<int, int>> edge[MAXN]; // 邻接表存图 pair为(距离, 终点)
queue<int> que; // STL队列
int dist[MAXN]; // 最短距离
bool vis[MAXN]; // 是否入队
void spfa(int src)
{
memset(dist, 0x3f, sizeof(dist));
dist[src] = 0;
que.push(src);
vis[src] = true;
while (!que.empty())
{
int cur = que.front();
que.pop();
vis[cur] = false;
for (auto next : edge[cur])
{
if (dist[next.second] > dist[cur] + next.first)
{
dist[next.second] = dist[cur] + next.first;
if (!vis[next.second])
{
que.push(next.second);
vis[next.second] = true;
}
}
}
}
}
利与 SPFA 判断负权回路
由于 SPFA 为 Bellman-Ford 算法的优化,所以它也可以判断负权回路,思路就是记录一个点被松弛了多少次。若没有负环一个点最多被松弛 $\left|V\right|-1$ 次,但如果有负环,一个点可以被松弛 $\infty$ 次。只需要对代码稍作修改即可:
- 无需将 dist 初始化为 $\infty$:因为我们只判负环,不在意距离是多少,因此无需初始化。
- 算法开始时需要将所有点入队:若只以一个点为起点,可能有些负环不与其连通无法判断到,因此得每个点都判断一遍。
- 增加一个 cnt 数组:储存被松弛多少次,一个点最多被松弛 $\left|V\right|-1$ 次,若大于它一定是出现了负环。
更改后的代码如下:
const int MAXN = 1e5 + 10, INF = 0x3f3f3f3f;
int E, V, S;
vector<pair<int, int>> edge[MAXN];
queue<int> que;
int dist[MAXN], cnt[MAXN];
bool vis[MAXN];
bool spfa()
{
for (int i = 1; i <= V; i++)
{
que.push(i);
vis[i] = true;
}
while (!que.empty())
{
int cur = que.front();
que.pop();
vis[cur] = false;
for (auto next : edge[cur])
{
if (dist[next.second] > dist[cur] + next.first)
{
dist[next.second] = dist[cur] + next.first;
cnt[next.second] = cnt[cur] + 1;
if (cnt[next.second] >= V)
return true;
if (!vis[next.second])
{
que.push(next.second);
vis[next.second] = true;
}
}
}
}
return false;
}
本文采用 CC BY-SA 4.0 许可,本文 Markdown 源码:Haotian-BiJi