最短路径模板——spfa算法

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

分析重边和自环的影响:

  1. 重边无影响,只要取最小权值的边即可,无非是多循环几遍。
  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算法。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值