spfa算法是Bellman-Ford的一个队列优化,朴素的Bellman-Ford算法中在执行每一次的松弛操作后,就会有一些顶点已经求到最短路,此后这些最短路的估计值就会一直保持不变,不再受后面松弛操作的影响,但是每次还要去判断是否需要松弛,所以浪费了时间
于是聪明的数学家就发明了SPFA算法
算法基本思路:
1.设置一个dis数组不断更新各个结点到起点的距离,开始时只有起点的dis值设为0,其余全设为正无穷
2.维护一个队列,保存待优化的结点(初始时只有一个原点),也就是dis值刚被更新的结点,自己变小了,但还未更新与它相邻的结点的dis值
3.队列不为空时就循环取出队首的点v,枚举与v相连的所有结点u->v,假设边的长度为len,判断dis[v]+len是否小于dis[u],小于则更新dis[u]为dis[v]+len,之后u有可能会更新其他点,所以如果u不在队列中,(这时候可以设置一个标记数组来判断是否在队列中),就加入队列,直到队列为空
如果一个点入队次数超过n,则有负权环
SPFA在形式上和广搜类似,但是说广搜如果一个点出了队列就不会再重新进入队列,但是SPFA中一个点有可能在出队后再次加进去,因为有可能这个点在更新过其他点之后,自己本身也被更新了,于是再次用来更新其他点
手动模拟SPFA算法
板子:
#include <bits/stdc++.h>
using namespace std;
const int inf = 0x3f3f3f3f;
const int maxn = 5e3 + 10;
int t, s, d;
struct EDGE {
int next;
int to;
int w;
} edge[maxn];
int cnt = 0;
int head[maxn], dis[maxn];
int vis[maxn];
void add(int u, int v, int w) {
edge[cnt].w = w;
edge[cnt].to = v;
edge[cnt].next = head[u];
head[u] = cnt++;
}
void spfa() {
memset(dis, inf, sizeof (dis));
memset(vis, 0, sizeof(vis));
queue<int> q;
q.push(0);
dis[0] = 0;
vis[0] = 1;
while (!q.empty()) {
int now = q.front();
q.pop();
vis[now] = 0;
for (int i = head[now]; i != -1; i = edge[i].next) {
int to = edge[i].to;
if (dis[to] > dis[now] + edge[i].w) {
dis[to] = dis[now] + edge[i].w;
if (vis[to]!=-1) {
vis[to] = 1;
q.push(to);
}
}
}
}
int ans = inf;
int p;
for (int i = 1; i <= d; ++i) {
cin >> p;
ans = min(ans, dis[p]);
}
cout<<ans<<endl;
}
int main() {
int a, b, time;
while (cin >> t >> s >> d) { //有T条路,与家相邻的城市有s个,草儿想去的地方有d个
memset(head, -1, sizeof(head));
cnt = 0;
for (int i = 0; i < t; i++) {
cin >> a >> b >> time;
add(a, b, time);
add(b, a, time);
}
int u;
for (int i = 0; i < s; i++) {
cin >> u;
add(u, 0, 0);
add(0, u, 0);
}
spfa();
}
}