题目
给定一个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
SPFA算法说明
首先要知道SPFA算法是bellman_ford算法的队列优化版
好处:
减少不必要的遍历 因为bellman算法要遍历每条边,而其实要更新的只是之前更新过的结点的邻节点(没更新过的边相当于根本不关联,遍历根本没有意义) 同时,因为负边的存在,b算法会进行毫无意义的更新(从INF到INF减去负权边),所以最后判定是否有最短路径不能用dis[N]==INF
,而是要dis[N]>INF/2
,而对于SPFA算法,只更新邻节点,也就意味着只会更新联通的点,就不会存在无意义的更新,因此最后只需要dis[N]==INF
即可
缺点:
不能在限定步数的情况下进行搜索,bellman算法遍历几次就限定了几步,而SPFA则是只要堆不为空就一直进行弹出/更新/加入的操作,因此一旦有负权换就会无限循环. 为了避免此情况,可以记录每一个节点的更新次数,每一次更新其实意味着该节点到源点的最短路径多了一步,一旦有点记录值大于边数则一定存在负权换,此时赶紧退出循环,提示存在负权环.
源代码
import java.util.*;
class Main{
static int INF=0x3f3f3f3f;
static int N=100010;
static int n;
static int m;
static int[] e=new int[N];
static int[] h=new int[N];
static int[] ne=new int[N];
static int index=0;
static boolean[] st=new boolean[N];
static int[] dis=new int[N];
static int[] w=new int[N];
public static void main(String[] args){
Scanner sc=new Scanner(System.in);
n=sc.nextInt();
m=sc.nextInt();
while(m-->0){
int x=sc.nextInt();
int y=sc.nextInt();
int z=sc.nextInt();
add(x,y,z);
}
int res=SPFA();
if(res==-1) System.out.print("impossible");
else System.out.print(res);
}
public static void add(int a,int b,int c){
e[++index]=b;
ne[index]=h[a];
h[a]=index;
w[index]=c;
}
public static int SPFA(){
Arrays.fill(dis,INF);
dis[1]=0;
Queue<Integer> q=new LinkedList<Integer>();
q.add(1);
while(!q.isEmpty()){
int t=q.poll();
st[t]=false;
for(int i=h[t];i!=0;i=ne[i]){
int j=e[i];
if(dis[j]>dis[t]+w[i]){
dis[j]=dis[t]+w[i];
if(!st[j]) {
q.add(j);
st[j]=true;
}
}
}
}
if(dis[n]==INF) return -1;
return dis[n];
}
}
变量设置
- 因为要用到相邻的结点,所以用邻接表存储 要用到
index
h[]
e[]
ne[]
- 因为邻接表只能存储边的连接关系,为了存储边的值,要新建一个数组w[index] 依旧是用index所以
- 所有最短路径一定要有一个
dis[n]
INF=0x3f3f3f3f - 因为某个结点可能是多个节点的邻节点,所以很可能进行多次遍历,也就意味着可能多次加入队,而其实我们只需要保证一个在队中即可,只要更新数值就够了 所以有st[]来保存是否在队中
关键步骤
- 对变量进行初始化 包扩dis的INF赋值/初始节点距离dis[1]==0/1节点入队
- 队非空时 弹出队头,标记出队,遍历邻节点,满足松弛就
立刻
更新,然后马上判断是否已经在队中,不在则入队,并标记(也就是任何出队入队都要进行标记操作) - 判断目标几点距离是否为INF