道路和航路 (SPFA算法)(Bellman-Ford算法的优化)

问题描述
农夫约翰正在针对一个新区域的牛奶配送合同进行研究。他打算分发牛奶到T个城镇(标号为1..T),这些城镇通过R条标号为(1..R)的道路和P条标号为(1..P)的航路相连。

每一条公路i或者航路i表示成连接城镇Ai(1<=A_i<=T)和Bi(1<=Bi<=T)代价为Ci。每一条公路,Ci的范围为0<=Ci<=10,000;由于奇怪的运营策略,每一条航路的Ci可能为负的,也就是-10,000<=Ci<=10,000。

每一条公路都是双向的,正向和反向的花费是一样的,都是非负的。

每一条航路都根据输入的Ai和Bi进行从Ai->Bi的单向通行。实际上,如果现在有一条航路是从Ai到Bi的话,那么意味着肯定没有通行方案从Bi回到Ai。

农夫约翰想把他那优良的牛奶从配送中心送到各个城镇,当然希望代价越小越好,你可以帮助他嘛?配送中心位于城镇S中(1<=S<=T)。

输入格式
输入的第一行包含四个用空格隔开的整数T,R,P,S。

接下来R行,描述公路信息,每行包含三个整数,分别表示Ai,Bi和Ci。

接下来P行,描述航路信息,每行包含三个整数,分别表示Ai,Bi和Ci。

输出格式
输出T行,分别表示从城镇S到每个城市的最小花费,如果到不了的话输出NO PATH。
样例输入
6 3 3 4
1 2 5
3 4 5
5 6 10
3 5 -100
4 6 -100
1 3 -10
样例输出
NO PATH
NO PATH
5
0
-95
-100
数据规模与约定
对于20%的数据,T<=100,R<=500,P<=500;

对于30%的数据,R<=1000,R<=10000,P<=3000;

对于100%的数据,1<=T<=25000,1<=R<=50000,1<=P<=50000。

这题乍看之下是一个有向图和无向图的混合型,但是,我们可以知道在最短路,对于一条正权边,能经过的正权边当然是越少越好,可是题中有负权,但是题目说了有负权的边是有向边,也就是说只会经过一次(排除负环情况),这个就只能用Bellman-Ford算法,这个算法之前有讲过,还有一个优化,称为SPFA算法,但是没有详细讲这个优化,这次就算用这个算法解决这道题

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;
public class Main 
{   
    static int INF=100000*100000+1;
    public static void main(String[] args) 
    {
        Scanner sc=new Scanner(System.in);
        int T=sc.nextInt();
        int R=sc.nextInt();
        int P=sc.nextInt();
        int S=sc.nextInt();//四个题设要求
        ArrayList list[]=new ArrayList[T+1];//邻接表
        int x,y,w;
        for(int i=0;iif(list[x]==null)list[x]=new ArrayList<>();
            if(list[y]==null)list[y]=new ArrayList<>();
            list[x].add(new Edge(y,w));
            list[y].add(new Edge(x,w));//对于无向边
        }
        for(int i=0;iif(list[x]==null)list[x]=new ArrayList<>();
            list[x].add(new Edge(y,w));//对于有向边,观察有向边和无向边的建立由什么不同之处
        }
        int d[]=new int[T+1];//所有S到其他的可达的点的最短路都将存储在这里

        boolean inQ[]=new boolean[T+1];//如果一个点入队,则置为true
        for(int i=1;i<=T;i++)d[i]=INF;//初始化均为无穷大
        d[S]=0;//自己到自己的距离为0
        inQ[S]=true;//将起点入队的标志
        Queue queue=new LinkedList<>();
        queue.add(S);

        while(!queue.isEmpty())
        {
            int s=queue.poll();//出队
            inQ[s]=false;//标志
            if(list[s]==null)continue;//假设一个点只有入度没有出度,那么在上面的建图过程中是没有赋予ArrayList这个对象的,只有一个引用而已,防止出现运行错误
            for(int i=0;i<list[s].size();i++)
            {
                Edge e=list[s].get(i);//顺次获得与s邻接的一条边的信息
                if(d[s]d[s]+e.w)//松弛造作
                {
                    d[e.to]=d[s]+e.w;
                    if(!inQ[e.to])//如果一个点的最短路被修改了信息,但没有入队,,那么就将其入队
                    {
                        queue.add(e.to);
                        inQ[e.to]=true;
                    }
                }
            }
        }
        for(int i=1;i<=T;i++)
        {
            if(d[i]==INF)//说明这点一次都没有经过松弛操作,那么说明这个点不可达
                System.out.println("NO PATH");
            else
                System.out.println(d[i]);

        }
    }
}
class Edge//边的定义
{
    int to,w;
    Edge(int to,int w)
    {
        this.to=to;
        this.w=w;
    }
}

对于这个算法,我们可以这样理解,之所以要把一个经过松弛操作的点入队(假设之前没有在队列)是因为这个点的更新会对后续的点的最短路的更新产生影响,假设每个点都存在最短路或者不可达,那么经过若干轮循环,松弛操作将不会再发生,也因而,不会再有入队的情况,队列最终为空,因为题目要求了不会有负环。假如有负环,那么某些点的更新操作会一直进行会趋向于无穷小,会产生死循环,因为队列会一直不为空,对于这种情况的侦测,我们可以在设置一个数组update[T+1]来记录一个点的更新次数,如果一个点的更新次数超过了T-1次, 说明这个图存在负环,以此来终止程序。

你可能感兴趣的:(算法分析,最短路)