spfa算法
spfa算法就是队列优化的Bellman-Ford算法
Bellman-Ford算法中,根据松弛函数dis[b]=min(dis[b],dis[a]+w)
,每次都扫描M条边,扫描N-1次,时间复杂度是O(MN)
由于每条边也可能存在使用多次的情况,所以也不能根据边的使用次数进行优化。因此我们更改优化思路,根据松弛函数dis[b]=min(dis[b],dis[a]+w)
,当我们每次要修改dis[b]时,必然是因为dis[a]发生了变化,然后再调用边(a,b),即仅仅因为dis[a]修改才会涉及dis[b]的修改。
因此,我们用一个队列进行维护,从起点开始,将每次修改的a点放入队列,依次拿出来队头,将与其相连的顶点进行修改。
因此,对比bellman-ford算法,spfa算法中我们需要知道图的存储结构,而不仅仅是每条边的信息才行,因为我们要根据一个顶点找到与它相连的所有的边。出度
spfa算法步骤:
queue <— 起点s
while queue 不为空
(1) t <– 队头
queue.pop()
(2)用 t 更新所有出边 t –> b,权值为w
queue <– b (若该点被更新过,则拿该点更新其他点)
为了避免队列中出现重复顶点的现象,我们用一个bool数组维护,当为true时说明已经加入队列等待更新,就不用再加入队列了。
时间复杂度 一般:O(m) 最坏:O(nm)
题目描述
给定一个n个点m条边的有向图,图中可能存在重边和自环, 边权可能为负数。
请你求出1号点到n号点的最短距离,如果无法从1号点走到n号点,则输出impossible。
数据保证不存在负权回路。
输入格式
第一行包含整数n和m。
接下来m行每行包含三个整数x,y,z,表示存在一条从点x到点y的有向边,边长为z。
输出格式
输出一个整数,表示1号点到n号点的最短距离。
如果路径不存在,则输出”impossible”。
数据范围
1≤n,m≤105,
图中涉及边长绝对值均不超过10000。
输入样例:
3 3
1 2 5
2 3 -3
1 3 4
输出样例:
2
分析重边和自环的影响:
- 重边无影响,只要取最小权值的边即可,无非是多循环几遍。
- 自环也是回路,但是题目说了无负权回路,所以可以忽略正权回路的影响了,因为权值越加越大,是不会选择这条路的。
算法实现
#include <iostream>
#include <cstring>
using namespace std;
const int N=1e5+10,M=1e5+10,INF=0x3f3f3f3f;
int h[N],e[M],w[M],ne[M],idx;
int dis[N];
int q[N],hh,tt=-1;//手写队列
bool vis[N]; //判断顶点是否已经加入队列
int n,m;
void add(int a,int b,int c)
{
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
void spfa(int s)
{
memset(dis,0x3f,sizeof dis);
dis[s]=0;
q[++tt]=s,vis[s]=true; //默认第一个距离已经被修改,加入队列
//vis数组进行优化,当队列中已有该结点时,就不用再插入了,即上次这次合并起来一起更新
while (hh<=tt) {
int v=q[hh++];
vis[v]=false;
for (int i=h[v];i!=-1;i=ne[i]) {//更新所有的dis[b]
int j=e[i]; //v->j的边,权值为w[i]
if (dis[j] > dis[v]+w[i]) {
dis[j]=dis[v]+w[i]; 由于是只有v顶点更改后才会更改j顶点的,所以不存在v顶点的dis[v]是INF的情况
if (!vis[j]) q[++tt]=j,vis[j]=true; //也就是不需要像bellman-ford算法那样if(dis[a]!=INF)判断了
}
}
}
}
int main()
{
memset(h,-1,sizeof h);
scanf("%d%d",&n,&m);
int a,b,c;
for (int i=0;i<m;i++) {
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);//有向图:a->b,权值为c
}
spfa(1);//求起点为1号结点到其它所有结点的距离
dis[n]==INF?puts("impossible"):printf("%d",dis[n]);
return 0;
}
spfa算法相比bellman-ford算法,还少了修改dis[b]时对dis[a]的判断:if(dis[a]!=INF)
因为是只有a顶点更改后才会更改b顶点的,所以不存在a顶点的dis[a]是INF的情况。
除非有边的权值是INF(
不太可能),或者起点dis[s]没有初始化,是INF。
spfa算法是bellman-ford算法的优化,
但是不能统计在某个路径长度下的路径权值最小的情况,
而且Bellman-ford算法就算在有负权回路的情况下,虽然得不到该路径上的正确答案,但是能够退出循环,
而当使用spfa算法时,如果存在负权回路的情况下,那么当i->j的路径上存在该负权回路时,就会因为队列不空一直循环,路径最小最终为无穷小。
假想一种情况:
当出现自环且权值为负时,自己在队头出队列,然后更新完自己到自己的边后,自己又入队列,如此无限循环。
因此,只有在已知图中不存在负权回路,或者在i->j的路径上不存在负权回路时,才能使用spfa算法。