前言:SPFA(Shortest Path Faster Algorithm)算法是求单源最短路线的一种算法,它是Bellman-ford的队列优化,它是一种十分高效的最短路算法。
spfa 其实可以看作 Dijkstra + 队列 的优化
知识储备:Dijkstra 与 队列
spfa通过队列优化,可以减少不必要的relax(松弛)!
本篇博客将以c++ STL写法实现spfa,当然您也可以通过结构体来实现(个人认为STL写法更加方便与简洁)!
接下来——》直奔主题!!!
如何存储信息(或如何构建图):
const int N = 10009;
vector<pair<int ,int> >s[N];
int n,m;//n个点,m条线路
cin>>n>>m;
for(int i = 0;i<m;i++)
{
int x,y,z;
scanf("%d %d %d",&x,&y,&z);
s[x].push_back(make_pair(y,z));
s[y].push_back(make_pair(x,z));//此行代码是为 实现双向路,若不是请将其删掉!
}
x,y ,z 指的是 :x——》(到) y 权值为z。
学习部分:
vector<pair<int,int> >s[N]
push_back
make_pair
如果您对以上三个 其中任何一个不了解,请您在CSDN上搜索 :
- vector
- pair
- vector pair
简单学习一下,了解是什么操作就ok!(如果可以学会,那就更加不错了)
与Dijkstra不同,spfa通过栈进行优化(都进行类DFS搜索过程——》relax(松弛)操作)
先请看Dijkstra的写法:
dis[1] = 0;
vis[1] = 1;
for (int i = 1; i <= n; i++)
{
int ans = inf, k = 0;
for (int j = 1; j <= n; j++)
{
if (!vis[j] && dis[j] < ans)
{
k = j;
ans = dis[j];
}
}
vis[k] = 1;
if (ans == inf)// inf = 0x3f3f3f3f
break;
for (int j = 1; j <= n; j++)
{
if (!vis[j] && dis[j] > dis[k] + d[k][j])
dis[j] = dis[k] + d[k][j];
}
}
printf("%d\n", dis[n]);
然后再看spfa的写法:
vis[1] = 1;
d[1] = 0;
queue<int> q;
q.push(1);
while(!q.empty())
{
int now = q.front();
q.pop();
vis[now] = 0;
for(int i = 0;i<s[now].size();i++)
{
int v = s[now][i].first;
if(d[v]>d[now]+s[now][i].second)
{
d[v] = d[now] + s[now][i].second;
if(vis[v]==1)
continue;
vis[v] = 0;
q.push(v);
}
}
}
printf("%d\n",d[n]);
比较一下吧,有什么不一样?
我猜你已经发现了,spfa就是Dijkstra的基础上加入队列进行优化
大家理解spfa了嘛?
没理解也没有关系,这一点内容确实有点难以理解,那么请你结合着spfa算法板子 从头到尾看一遍,或许你就可以啃下spfa这块硬骨头了!(加油!!!)
spfa算法板子:(同样也是一道板子题——》畅通工程续)
#include<iostream>
#include<vector>
#include<algorithm>
#include<queue>
const int maxn = 205;
using namespace std;
vector<pair<int, int> >E[maxn];
int n, m;
int d[maxn], inq[maxn];
void init()
{
for (int i = 0; i < maxn; i++)E[i].clear();
for (int i = 0; i < maxn; i++)inq[i] = 0;
for (int i = 0; i < maxn; i++)d[i] = 1e9;
}
int main()
{
while (cin >> n >> m)//多组输入
{
init();
for (int i = 0; i < m; i++)
{
int x, y, z;
scanf("%d %d %d", &x, &y, &z);
E[x].push_back(make_pair(y, z));
E[y].push_back(make_pair(x, z));
}
int s, t;//起点与终点
//scanf("%d %d" , &s, &t);
s = 1,t = n;
queue<int> q;
q.push(s), d[s] = 0, inq[s] = 1;
while (!q.empty())
{
int now = q.front();
q.pop();
inq[now] = 0;
for (int i = 0; i < E[now].size(); i++)
{
int v = E[now][i].first;
if (d[v] > d[now] + E[now][i].second)
{
d[v] = d[now] + E[now][i].second;
if (inq[v] == 1)continue;
inq[v] = 0;
q.push(v);
}
}
}
if (d[t] == 1e9)
printf("-1\n");
else printf("%d\n", d[t]);
}
}
接下来,我将以一道例题收尾
题目如下:
在每年的比赛里,所有进入决赛的同学都会获得一件很漂亮的 t-shirt。但是每当我们的工作人员把上百件的衣服从商店运回到赛场的时候,却是非常累的!所以现在他们想要寻找最短的从商店到赛场的路线,你可以帮助他们吗?
输入格式
输入包括多组数据。
每组数据第一行是两个整数 N、M(N≤100,M≤10000),NNN 表示大街上有几个路口,标号为 111 的路口是商店所在地,标号为 N 的路口是赛场所在地,M 则表示在成都有几条路。N=M=0表示输入结束。接下来 M 行,每行包括 3 个整数 A,B,C(1≤A,B≤N,1≤C≤1000),表示在路口 A 与路口 B 之间有一条路,我们的工作人员需要 C 分钟的时间走过这条路。
输入保证至少存在 1 条商店到赛场的路线。
输出格式
对于每组输入,输出一行,表示工作人员从商店走到赛场的最短时间。
样例输入
2 1 1 2 3 3 3 1 2 5 2 3 5 3 1 2 0 0
样例输出
3 2
AC代码如下:
#include<iostream>
#include<queue>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
const int N = 10009;
vector<pair<int ,int> >s[N];
int d[N],vis[N];
int n,m;
void init()
{
for(int i = 0;i<N;i++)s[i].clear();
for(int i = 0;i<N;i++)d[i] = 1e9;
for(int i = 0;i<n;i++)vis[i] = 0;
}
int main()
{
while(cin>>n>>m)
{
if(n==0&&m==0)
return 0;
init();
for(int i = 0;i<m;i++)
{
int x,y,z;
scanf("%d %d %d",&x,&y,&z);
s[x].push_back(make_pair(y,z));
s[y].push_back(make_pair(x,z));
}
vis[1] = 1;
d[1] = 0;
queue<int> q;
q.push(1);
while(!q.empty())
{
int now = q.front();
q.pop();
vis[now] = 0;
for(int i = 0;i<s[now].size();i++)
{
int v = s[now][i].first;
if(d[v]>d[now]+s[now][i].second)
{
d[v] = d[now] + s[now][i].second;
if(vis[v]==1)
continue;
vis[v] = 0;
q.push(v);
}
}
}
printf("%d\n",d[n]);
}
}
此题依旧是板子题(T1600 最短路)
如果你看到了这个了
那么 恭喜你,你已入门!!!(滑稽保命,哈哈!)
最后,感谢您的阅读!
既然为梦想选择了远方,便没有回头路可以走。所以,要么战死沙场,要么狼狈回乡。