851. spfa求最短路 Java题解 (spfa)

32 篇文章 1 订阅

输入样例:

3 3
1 2 5
2 3 -3
1 3 4

输出样例:

2

算法思路:

spfa最短路算法是对bellmanFord算法的优化,用于求单源最短路间的带负权的算法,一般时间复杂度为O(m),但最坏情况为O(nm)。spfa要求图中不能有负环(否则回无限循环)。

belllmanFord算法核心是 遍历每条边:dis[b] = min(dis[b], dis[a] + c),含义为:b到源点的最短路为当前b的最短路与将a点作为中间桥梁时,a点的最短路加上ab边的权值的最小值。由于bellmanFord算法每次从源点向外扩展一步,所以有很多边虽然遍历了,但没有意义,没有更新。即取决于dis[a]是否更新过,用宽搜优化。spfa算法将每次从源点发生变化的点入队,因为只有某些边缩小了,之后的边才会借此更新。

spfa算法步骤:(一初二迭三标四更)

  1. 初始化dis,qu,vis
  2. 只要队列中还有发生更新过的点就迭代
  3. 出队标记
  4. 更新点t的邻接边并选择性入队
  • spfa中vis数组的作用:标记某点是否在队列中,只有某点不在队列中,更新时遇到变化的点才加入队列,否则若如果队列中已经存在了该点,就不再入队了,否则会出现重复点,造成无意义的迭代,耗时。
  • dijkstra与spfa算法中vis数组的区别:前者是为了标记某点是否已经是从源点到当前点距离最小的点(是否已经用该点更新过其他点)。后者是为了标记该点是否在队列中存在。
  • dijkstra与spfa算法中使用队列的区别:前者使用的优先队列,元素只进不出,后者使用的普通队列;前者的使用队列是为了快速找出未被访问的点中距与源点最近的点,后者是为了记录更新过的点。若有负环,会一直更新,陷入死循环。
  • bellmanFord与spfa返回值判断时的区别:前者是统一迭代,无论是否两点间有路径,都会更新。而后者是通过邻接表的形式存储图,只会更新与源点相连的边。

Java代码:

import java.io.*;
import java.util.*;

public class Main {
	static int n, m;
	static final int N = 100005;
	static int []e = new int[N];
	static int []ne = new int[N];
	static int []h = new int[N];
	static int []w = new int[N];
	static int idx;
	static int []dis = new int[N];
	static boolean []vis = new boolean[N];  //标记当前点是否在队列中,在时只需要更改dis数值,不在时才需要入队,否则重复判断耗时
	static final int INF = 0x3f3f3f3f;
	
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		String[] split = br.readLine().split(" ");
		n = Integer.parseInt(split[0]);
		m = Integer.parseInt(split[1]);
		Arrays.fill(h, -1);
		
		for(int i = 0; i < m; i++) {
			split = br.readLine().split(" ");
			int a = Integer.parseInt(split[0]);
			int b = Integer.parseInt(split[1]);
			int c = Integer.parseInt(split[2]);
			add(a, b, c);
		}
		
		spfa();
	}
	
	public static void spfa() {
		Arrays.fill(dis, INF);  // 1.初始化dis,qu,vis
		dis[1] = 0;
		Queue<Integer> qu = new LinkedList<>(); 
		qu.add(1);     // 1号点距离更新了,其出度的点都会伴随着更新
		vis[1] = true; // 1号点已在队列中
		
		while(!qu.isEmpty()) {  // 2.只要队列中还有发生更新过的点就迭代
			int t = qu.poll();  //3.出队标记
			vis[t] = false;     // 标记队列中不存在的点t
			
			for(int i = h[t]; i != -1; i = ne[i]) { //  4.1更新点t的邻接边
				int j = e[i];
				if(dis[j] > dis[t] + w[i]) {
					dis[j] = dis[t] + w[i];
					if(!vis[j]) {    // 4.2若更新后的点队列中不存在则入队
						qu.add(j);
						vis[j] = true;
					}
				}
			}
		}
		
		if(dis[n] == INF) System.out.println("impossible"); // 路径不通的边不会更新,只需判断等于inf即可
		else System.out.println(dis[n]);
	}
	
	public static void add(int a, int b, int c) {
		e[idx] = b;
		w[idx] = c;
		ne[idx] = h[a];
		h[a] = idx++;
	}
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值