spfa算法最短路

介绍

SPFA(Shortest Path Faster Algorithm) 算法是 Bellman-Ford算法 的队列优化算法的别称,通常用于求含负权边的单源最短路径,以及判负权环。SPFA 最坏情况下复杂度和朴素 Bellman-Ford 相同,为 O(VE)。

为了避免最坏情况的出现,在正权图上应使用效率更高的Dijkstra算法。

算法特点

1.时间复杂度比Dijkstra和ford低。
2.能够计算负权图的问题。
3.能够判断是否有负环。
4.与bfs算法比较,复杂度相对稳定,但在稠密图中复杂度比Dijkstra算法差。

算法思想

简洁起见,我们约定加权有向图G不存在负权回路,即最短路径一定存在。用数组d记录每个结点的最短路径估计值,而且用邻接表来存储图G。

动态逼近法:
1.设立一个先进先出的队列用来保存待优化的节点。
2.优化时每次取出队首节点u,并且用u点当前的最短路径估计值对离开u点所指向的节点v进行松弛操作。如果v点的最短路径估计值有所调整,且v点不在当前的队列中,就像v点放入队尾。
3.不断从队列中取出节点来进行松弛操作。直至队列为空。

期待的时间复杂度为O(ke),其中k为所有顶点进队的平均次数,可以证明k一般小于等于2.

定理:只要最短路径存在,上述spfa算法必定能求出最小值。
证明:每次将点放入队尾,都是经过松弛操作达到的。换言之,每次的优化将会有某个点b的最短路径估计值d[v]变小。所以算法的执行会使d越来越小。由于之前假定图中不存在负权回路,所以每个节点都会有最短路径值。因此,算法不会无限执行下去,随着d值得逐渐减小,直到到达最短路径值时,算法结束,这时的最短路径估计值就是对应节点的最短路径值。

实际上,一个点如果进入队列达到n次,则表明图中存在负环,没有最短路径。

段凡丁论文中的复杂度证明 (O(kE),k 是小常数)是错误的,在此略去。该算法的最坏复杂度为 O(VE)。

对spfa的一个很直观的理解就是由无权图的BFS转化而来。在无权图中,BFS首先到达的顶点所经历的路径一定是最短路(也就是经过的最少顶点数)。所以此时利用数组记录节点可以使每个顶点只进队一次,但在带权图中,最先到达的顶点所计算出来的路径不一定是最短路。一个解决方法是放弃数组,此时所需时间是指数级的。所以不能放弃数组,而是在处理一个已经在队列中且当前所得的路径比原来更好的顶点时,直接更新最优解。

算法实现

import java.util.Queue;
import java.util.Scanner;
import java.util.concurrent.LinkedBlockingDeque;

public class Main {
    static class Edge{
        int v,w,next;
        Edge(int v,int w,int next){
            this.v=v;
            this.w=w;
            this.next=next;
        }
    }
    static final int N= 100500;
    static final int M=1000050;
    static final int INF=0x3f3f3f3f;
    static int n,m,s,t,u,v,w,cnt;
    static int[] head=new int[N];
    static Edge[] edges=new Edge[M];
    static int[] dis=new int[N];
    static boolean[] vis=new boolean[N];
    static int[] inq=new int[N];
    static void init(){
        cnt=0;
        for(int i=1;i<=n;i++){
            head[i]=-1;
        }
    }
    static void addEdge(int u,int v,int w){
        edges[cnt]=new Edge(v,w,head[u]);
        head[u]=cnt++;
        edges[cnt]=new Edge(u,w,head[v]);
        head[v]=cnt++;
    }
    static boolean spfa(){
        for(int i=1;i<=n;i++){
            vis[i]=false;
            inq[i]=0;
            dis[i]=INF;
        }
        dis[s]=0;
        vis[s]=true;
        Queue<Integer> q=new LinkedBlockingDeque<>();
        q.add(s);
        inq[s]=1;
        while(!q.isEmpty()){
            int u=q.poll();
            vis[u]=false;
            for(int i=head[u];i!=-1;i=edges[i].next){
                int v=edges[i].v;
                int w=edges[i].w;
                if(dis[v]>dis[u]+w){
                    dis[v]=dis[u]+w;
                    if(!vis[v]){
                        vis[v]=true;
                        q.add(v);
                        if(++inq[v]>n){
                            return false;
                        }
                    }
                }
            }
        }
        return true;
    }
    public static void main(String[] args) {
        Scanner cin=new Scanner(System.in);
        n=cin.nextInt();
        m=cin.nextInt();
        s=cin.nextInt();
        t=cin.nextInt();
        init();
        while(m-->0){
            u=cin.nextInt();
            v=cin.nextInt();
            w=cin.nextInt();
            addEdge(u,v,w);
        }
        spfa();
        System.out.println(dis[t]);
    }
}



应用

POJ3259 wormholes

优化策略

SLF

Small Lable First 策略。设要加入的节点是j,队首元素为I,若dist(j)<dist(i),则将j插入队首,否则插入队尾。

LLL

Large Label Last 策略。设队首元素为I,队列中所有dist值得平均值为x,若dist(i)>x则将i插入到队尾,查找下一元素,直至找到某一i使得dist(i)<=x,则将i出兑进行松弛操作。

比较

SLF和LLF在随机数据上表现优秀,但是在正权图中最坏情况是O(VE),在负权图中最坏情况达到指数级复杂度。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值