SPFA

目录

 

SPFA

原理

代码实现:

优化

判断负环

方法一

方法二

方法三

SPFA与BFS

SPFA与DFS

 


SPFA

SPFA 算法通常用于求含负权边的单源最短路径,以及判负权环。在正权图上应使用效率更高的Dijkstra算法。若给定的图存在负权边,类似Dijkstra等算法便没有了用武之地

 

原理

我们约定加权有向图G不存在负权回路,即最短路径一定存在。用数组d记录每个结点的最短路径估计值,而且用邻接表(或者前向星存图)来存储图G。我们采取的方法是动态逼近法:设立一个先进先出的队列用来保存待优化的结点,优化时每次取出队首结点u,并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛操作,如果v点的最短路径估计值有所调整,且v点不在当前的队列中,就将v点放入队尾。这样不断从队列中取出结点来进行松弛操作,直至队列空为止。

代码实现:

//------邻接表------------(可为前向星存图)
struct edge
{
    int to;//终点
    int w;//权值
    edge *next;//指向下一条边的指针
};
edge *p[maxn];//每条边的头结点.初始化NULL
//-------------------------

int n,m,s;
int vis[maxn];//是否在队列中
int dis[maxn];
void SPFA(int s)
{
    const int INF = 0x3f3f3f3f;
    for(int i=0;i<=n;i++)
    {
        vis[i]=0;
        dis[i]=INF;
    }//初始化
    dis[s]=0;
    vis[s]=1;
    queue<int>q;//创建队列
    q.push(s);
    while(!q.empty())
    {
        int from = q.front();
        q.pop();
        vis[from]=0;
        edge *t=p[from];
        while(t!=NULL)
        {
            int to=t->to;//终点
            int w=t->w;
            if(w+dis[from]<dis[to])
            {
                dis[to]=w+dis[from];//更新
                if(!vis[to]){
                    vis[to]=1;
                    q.push(to);//入队
                }
            }
            t=t->next;
        }
    }
    for(int i=1;i<=n;i++)
        cout<<dis[i]<<' ';//最短路
}

优化

同Dijkstra一样,SPFA一样可以采用堆优化的方式,虽然看起来堆优化后的SPFA真是越来越像   djikstra 了  QWQ~~ 

就不贴代码了

判断负环

如果图中存在负环,那么在求最短路径时队列永远不会空,每次都会更新那个负环上的边。

方法一

在松弛过程中记录起点到每个点的当前最短路径上的边的条数t,如果不存在负环,则t<=n-1,所以如果t>=n,则一定存在负环

代码

int cnt=1;
int n,m,s;
int vis[maxn];//是否在队列中
int dis[maxn];

//-------前向星存图-------
struct edge
{
    int next;
    int to;
    int w;
}p[maxm];
int h[maxn];
void add(int a,int b,int w)
{
    p[cnt].next=h[a];
    p[cnt].to=b;
    p[cnt].w=w;
    h[a]=cnt;
    cnt++;
}
//----------------------

bool SPFA(int s)
{
    int t[maxn]={0};
    for(int i=0;i<=n;i++)
    {
        vis[i]=0;
        dis[i]=INF;
    }
    dis[s]=0;
    vis[s]=1;
    queue<int>q;
    q.push(s);
    while(!q.empty())
    {
        int from = q.front();
        q.pop();
        vis[from]=0;
        for(int i=h[from];i;i=p[i].next)
        {
            int to=p[i].to;
            int w=p[i].w;
            if(dis[to]>dis[from]+w)
            {
//---------------------关键------------------------
                t[to]=t[from]+1;
                if(t[to]==n)
                    return false;//存在负环
//-------------------------------------------------
                dis[to]=dis[from]+w;
                if(!vis[to])
                {
                    vis[to]=1;
                    q.push(to);//入队
                }
            }
        }
    }
    return true;//不存在负环
}

方法二

如果图中不存在负环,那么在SPFA过程中,每个点进入队列的次数ans<=n的,反之,必定存在负环

但是:这种方法比第一种方法慢,方法一只需绕环一次就能够判断,而这个方法需要绕n次环,不推荐使用

代码

int cnt=1;
int n,m,s;
int vis[maxn];//是否在队列中
int dis[maxn];

//-------前向星存图-------
struct edge
{
    int next;
    int to;
    int w;
}p[maxm];
int h[maxn];
void add(int a,int b,int w)
{
    p[cnt].next=h[a];
    p[cnt].to=b;
    p[cnt].w=w;
    h[a]=cnt;
    cnt++;
}
//----------------------
bool SPFA(int s)
{
    int t[maxn]={0};
    for(int i=0;i<=n;i++)
    {
        vis[i]=0;
        dis[i]=INF;
    }
    dis[s]=0;
    vis[s]=1;
    queue<int>q;
    q.push(s);
    while(!q.empty())
    {
        int from = q.front();
        q.pop();
        vis[from]=0;
        for(int i=h[from];i;i=p[i].next)
        {
            int to=p[i].to;
            int w=p[i].w;
            if(dis[to]>dis[from]+w)
            {
                dis[to]=dis[from]+w;
                if(!vis[to])
                {
              //----------关键代码------
                    t[to]++;
                    if(t[to]>n)
                        return false;//存在负环
                //---------------------
                    vis[to]=1;
                    q.push(to);//入队
                }
            }
        }
    }
    return true;
}

方法三

用dfs来判断,在下面说

SPFA与BFS

当一张图从加权图变为无权图,显然SPFA算法就成为了一个BFS操作,只是除去了松弛操作。而在加权图中,因为每条边的权重并不相同,所以在BFS过程中,第一次到达的点的路径并不一定是最段路径,只得进行松弛操作,直到没有路径可以松弛

SPFA与DFS

显然上边的SPFA 是基于BFS的。

而我们知道,每一个BFS基本都可以变为DFS。相当于是把"先进先出"的队列换成了"先进后出"的栈

也就是说,每次都以刚刚松弛过的点来松弛其他的点。

在判断负环方面:如果能够松弛点 x 并且 x 还在栈中,那图中就有负环。一般来说的话,若存在负环,那么 dfs 会比 bfs 快

但是如果不存在负环,dfs 可能会严重影响求最短路的效率,要谨慎使用

代码

bool flag;
void SPFA(int s)
{
    vis[s]=1;
    for(int i=h[s];i;i=p[i].next)
    {
        int w=p[i].w;
        int to=p[i].to;
        if(w+dis[s]<dis[to])
        {
            dis[to]=w+dis[s];
            if(vis[to])
            {
                flag=false;//存在负环
                return;
            }
            SPFA(to);
        }
    }
    vis[s]=0;
}
//flag初始化true dis[i]=INF  dis[start]=0;


 

 

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值