输入样例:
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算法步骤:(一初二迭三标四更)
- 初始化dis,qu,vis
- 只要队列中还有发生更新过的点就迭代
- 出队标记
- 更新点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++;
}
}