SPFA详解

来源:互联网 发布:php 上传txt 编辑:程序博客网 时间:2024/05/29 11:20

引子

定义

SPFA是Shortest Path Faster Algorithm,是Bellman-Ford算法的改进版。和其他最短路算法一样,都是以松弛操作的三角形不等式为基础操作的。

优点

SPFA算法用途广,适应负权,还能判断正环和负环……在差分约束建模中也有重大用处……SPFA是个好东西

SPFA的实现

spfa有两种实现方式,一种是栈实现,一种是队列实现。
在有负环的情况下,栈比队列更快,但是如果没有负环的一般情况下,队列更快。

栈实现

用栈实现的spfa实际上跟dfs很像,实际上也可以说就是dfs。采用邻接表可以很好的实现,时间复杂度O(ne)

队列实现

先把源点进队,然后用源点扩展更新,能迭代的都进队……
就是用队列里的点去迭代其他点,被迭代的点再次进队。
时间复杂度O(ne)

SPFA的应用

spfa在noip中通常用于求最短路/判负环。求最短路,spfa相比Dijkstra、Bellman-Ford等算法有着优秀的时间复杂度和剪枝,还有自己的特色:可以判负环。因为有可以判负环的性质,一般还用在差分约束系统建模后的一些判断工作。

求最短路

单源最短路径模板题
这道题目n数据量10000,m数据量500000,明显floyd超时超空间,Dijkstra也超时。
所以我们用了spfa:

#include<bits/stdc++.h>#define maxn 100010#define maxm 500010#define maxint 2147483647using namespace std;inline int read(){    int num=0;    char c=getchar();    for(;c<'0'||c>'9';c=getchar());    for(;c>='0'&&c<='9';c=getchar())num=num*10+c-'0';    return num;}int n,m,s,top,dis[maxn*4];bool vis[maxn*4];struct node{    int nxt,len,order; }a[maxm*4];int ljhead[maxn*4];int q[maxn*3],head,tail=0;void addedge(int x,int y,int length){    top++;    a[top].len=length;    a[top].nxt=ljhead[x];    a[top].order=y;    ljhead[x]=top;}//邻接表插入边。void init(){    n=read();    m=read();    s=read();    for(int i=1;i<=m;i++)    {        int x,y,length;        x=read();        y=read();        length=read();        addedge(x,y,length);    }}void SPFA(){    for(int i=1;i<=n;i++)    {        dis[i]=maxint;        vis[i]=false;    }    dis[s]=0;    vis[s]=true;    int v;    head=0;tail=1;q[1]=s;    while(tail-head+1>0)    {        v=q[++head];        for(int i=ljhead[v];i;i=a[i].nxt)        if(dis[v]+a[i].len<dis[a[i].order])        {            dis[a[i].order]=dis[v]+a[i].len;            if(vis[a[i].order]==false)            {                vis[a[i].order]=true;                tail++;                q[tail]=a[i].order;            }        }        vis[q[head]]=false;    }}int main(){    init();    SPFA();    for(int i=1;i<=n;i++)    printf("%d ",dis[i]);    return 0;}

判断负环

负环模板题
这道题目是板子题啊,我们直接上dfs_spfa!

#include<bits/stdc++.h>#define maxn 200200#define INF 2e9using namespace std;inline int read(){    int num=0;    bool flag=true;    char c;    for(;c>'9'||c<'0';c=getchar())    if(c=='-')    flag=false;    for(;c>='0'&&c<='9';num=num*10+c-48,c=getchar());    return flag ? num : -num;}struct node{    int to,val;};int dis[maxn],t,n,m;bool vis[maxn],flag;vector<node>G[maxn];void insert(int x,int y,int w){    G[x].push_back((node){y,w});}void init(){    n=read();    m=read();    for(int i=0;i<=n;i++)    {        dis[i]=INF;        vis[i]=false;        G[i].clear();    }    for(int i=1;i<=m;i++)    {        int x=read();        int y=read();        int w=read();        if(w<0)        {            insert(x,y,w);        }        else        {            insert(x,y,w);            insert(y,x,w);        }    }}void spfa(int u){    vis[u]=true;    for(int i=0;i<G[u].size();i++)    {        int to=G[u][i].to;//是这条边对面的点        if(dis[u]+G[u][i].val<dis[to])        {            dis[to]=dis[u]+G[u][i].val;            if(vis[to])            {                flag=true;                return ;            }            else            {                spfa(to);            }        }    }    vis[u]=false;}int main(){    t=read();    while(t--)    {        init();        flag=false;        dis[1]=0;        spfa(1);        if(flag)        {            printf("YE5\n");        }        else        {            printf("N0\n");        }    }    return 0; }

但是可惜啊!tle3个点,O-2优化开了也没用!
我们需要思考下dfs_spfa的优化了!

判断负环的优化

既然我们只需要判断负环,那么就相当于我们需要找到一条权值和为负的回路。
既然我们只需要找到权值和为负的回路,那不妨使距离数组dis初始化为0。
这样处理后,第一次拓展只会拓展到与起点相连边权为负的边。
那么我们就分别枚举所有的点作为起点,如果已经找到一个负环就不再继续枚举。
根据SPFA,我们找到的负环一定包含当前枚举的这个点。
算法期望复杂度O(m)

#include<bits/stdc++.h>#define maxn 200200#define INF 2e9using namespace std;inline int read(){    int num=0;    bool flag=true;    char c;    for(;c>'9'||c<'0';c=getchar())    if(c=='-')    flag=false;    for(;c>='0'&&c<='9';num=num*10+c-48,c=getchar());    return flag ? num : -num;}struct node{    int to,val;};int dis[maxn],t,n,m;bool vis[maxn],flag;vector<node>G[maxn];void insert(int x,int y,int w){    G[x].push_back((node){y,w});}void init(){    n=read();    m=read();    for(int i=0;i<=n;i++)    {        dis[i]=0;//这里由于要枚举点,所以要赋值为0;        vis[i]=false;        G[i].clear();    }    for(int i=1;i<=m;i++)    {        int x=read();        int y=read();        int w=read();        if(w<0)        {            insert(x,y,w);        }        else        {            insert(x,y,w);            insert(y,x,w);        }    }}void spfa(int u)//spfa基本上没有什么变化{    if(flag)return;     vis[u]=true;    for(int i=0;i<G[u].size();i++)    {        if(flag)return;         int to=G[u][i].to;        if(dis[u]+G[u][i].val<dis[to])        {            dis[to]=dis[u]+G[u][i].val;            if(vis[to])            {                flag=true;                return ;            }            else            {                spfa(to);            }        }    }    vis[u]=false;}int main(){    t=read();    while(t--)    {        init();        flag=false;        dis[1]=0;        for(int i=1;i<=n;i++)        {            spfa(i);//枚举每一个点            if(flag)break;        }        if(flag)        {            printf("YE5\n");        }        else        {            printf("N0\n");        }    }    return 0; }

原来的程序运行时间是5112ms,优化后的程序运行时间312ms……

差分约束系统

我们在差分约束题目中会得出一系列代数约束关系,例如xy等,我们这时候可以通过这些约束关系建立有向图,然后利用spfa来判断最值、是否存在解或者有无穷多的解。
详见:差分约束详解by柴犬首相

高阶版敬请期待

GG