一、题目描述
某省自从实行了很多年的畅通工程计划后,终于修建了很多路。不过路多了也不好,每次要从一个城镇到另一个城镇时,都有许多种道路方案可以选择,而某些方案要比另一些方案行走的距离要短很多。这让行人很困扰。
现在,已知起点和终点,请你计算出要从起点到终点,最短需要行走多少距离。
Input
- 本题目包含多组数据,请处理到文件结束。
- 每组数据第一行包含两个正整数N和M(0<N<200,0<M<1000),分别代表现有城镇的数目和已修建的道路的数目。城镇分别以0~N-1编号。
- 接下来是M行道路信息。每一行有三个整 A,B,X(0<=A,B<N,A!=B,0<X<10000),表示城镇A和城镇B之间有一条长度为X的双向道路。
- 再接下一行有两个整数S,T(0<=S,T<N),分别代表起点和终点。
Output
- 对于每组数据,请在一行里输出最短需要行走的距离。如果不存在从S到T的路线,就输出-1.
Sample Input
3 3
0 1 1
0 2 3
1 2 1
0 2
3 1
0 1 1
1 2
Sample Output
2 -1
二、题解
方法一:双边 spfa
题目说 A 与 B 城市存在双向道路,所以使用邻接表的时候,需要加双边。由于不存在负环,如果边是点的两倍的话,用 spfa 效率会降低到 。
import java.util.*;
import java.math.*;
import java.io.*;
public class Main{
static int[] d;
static boolean[] inq;
static List<Edge>[] G;
static int INF = 0x3f3f3f3f;
static int spfa(int s, int e) {
Queue<Integer> q = new ArrayDeque<>();
Arrays.fill(d, INF);
d[s] = 0;
q.add(s);
inq[s] = true;
while (!q.isEmpty()) {
Integer now = q.poll();
inq[now] = false;
for (int i = 0; i < G.length; i++) {
for (Edge edge : G[i]) {
if (d[edge.to] > d[i] + edge.cost) {
d[edge.to] = d[i] + edge.cost;
if (inq[edge.to]) continue;
q.add(edge.to);
inq[edge.to] = true;
}
}
}
}
if (d[e] > INF / 2) return -1;
return d[e];
}
public static void main(String[] args) throws IOException {
Scanner sc = new Scanner(new BufferedInputStream(System.in));
BufferedWriter w = new BufferedWriter(new OutputStreamWriter(System.out));
while (sc.hasNext()) {
int V = sc.nextInt();
int E = sc.nextInt();
inq = new boolean[V+1];
d = new int[V+1];
G = new List[V+1];
for (int i = 0; i <= V; i++) {
G[i] = new ArrayList<Edge>();
}
for (int i = 0; i < E; i++) {
int a = sc.nextInt();
int b = sc.nextInt();
int c = sc.nextInt();
G[a].add(new Edge(b, c));
G[b].add(new Edge(a, c));
}
int s = sc.nextInt();
int e = sc.nextInt();
System.out.println(spfa(s, e));
}
}
static class Edge {
int to;
int cost;
public Edge(int to, int cost) {
this.to = to;
this.cost = cost;
}
}
}
复杂度分析
- 时间复杂度: ,
- 空间复杂度: ,
方法二:Dijkstra 算法
Dijkstra 算法适用于无负权图的单源最短路径问题,该算法使用一个优先队列来存储离源点最近的新节点,避免了每次遍历点的边集时,都去枚举不必要的点来更新最短路径 dist[i]
。
import java.util.*;
import java.math.*;
import java.io.*;
public class Main{
static int[] d;
static boolean[] inq;
static List<Edge>[] G;
static int INF = 0x3f3f3f3f;
static int dijkstra(int s, int e) {
Queue<Edge> q = new PriorityQueue<>((e1, e2) -> e1.cost - e2.cost);
Arrays.fill(d, INF);
d[s] = 0;
q.add(new Edge(s, 0));
while (!q.isEmpty()) {
Edge now = q.poll();
for (Edge edge : G[now.to]) {
if (d[edge.to] > d[now.to] + edge.cost) {
d[edge.to] = d[now.to] + edge.cost;
q.add(new Edge(edge.to, d[edge.to]));
}
}
}
if (d[e] > INF / 2) return -1;
return d[e];
}
public static void main(String[] args) throws IOException {
Scanner sc = new Scanner(new BufferedInputStream(System.in));
BufferedWriter w = new BufferedWriter(new OutputStreamWriter(System.out));
while (sc.hasNext()) {
int V = sc.nextInt();
int E = sc.nextInt();
inq = new boolean[V+1];
d = new int[V+1];
G = new List[V+1];
for (int i = 0; i <= V; i++) {
G[i] = new ArrayList<Edge>();
}
for (int i = 0; i < E; i++) {
int a = sc.nextInt();
int b = sc.nextInt();
int c = sc.nextInt();
G[a].add(new Edge(b, c));
G[b].add(new Edge(a, c));
}
int s = sc.nextInt();
int e = sc.nextInt();
System.out.println(dijkstra(s, e));
}
}
static class Edge {
int to;
int cost;
public Edge(int to, int cost) {
this.to = to;
this.cost = cost;
}
}
}
复杂度分析
- 时间复杂度: ,队列中最多存在 V 个顶点,需要取枚举每一条边。
- 空间复杂度: ,
题目链接:这里