您的位置:首页 > 编程语言 > Java开发

ALGO-5 最短路 — 单源最短路 SPFA 算法(java)

2018-02-11 08:21 423 查看
ALGO-5 最短路
问题描述给定一个n个顶点,m条边的有向图(其中某些边权可能为负,但保证没有负环)。请你计算从1号点到其他点的最短路(顶点从1到n编号)。输入格式第一行两个整数n, m。接下来的m行,每行有三个整数u, v, l,表示u到v有一条长度为l的边。输出格式共n-1行,第i行表示1号点到i+1号点的最短路。样例输入3 3
1 2 -1
2 3 -1
3 1 2样例输出-1
-2数据规模与约定对于10%的数据,n = 2,m = 2。对于30%的数据,n <= 5,m <= 10。对于100%的数据,1 <= n <= 20000,1 <= m <= 200000,-10000 <= l <= 10000,保证从任意顶点都能到达其他所有顶点。
题目分析:带负权边的单源最短路问题

算法分析:
题目的锦囊提示可以使用带堆优化的Dijkstra算法,因为此图的边数比点数的平方要少很多。但是根据题意,含负权边并且不含负环,这里我选用的是SPFA算法(Shortest Path Faster Algorithm)。SPFA是队列优化的Bellman-Ford算法,利用了队列的先进先出性质。按照本题的要求提一下思路:引入数组d [ ] ,[b]d [ i ] 表示始点到 i 的最短路
,初始赋值为无穷大,只有 始点的对应值为 0;引入数组 first [ ] ,记录前驱,初始赋值为-1,表示还没有知道前驱。接着将始点入队,每次读取一个入队元素并将其赋值给x,然后出队,并对所有相邻点进行松弛操作,松弛成功的点将其入队。 重复这个步骤,直到队列为空时算法结束。另外需要注意的是需要引入 boolean数组 vis [ ] ,用来标记点的状态,在读取、赋值、出队操作后要清除标记,表示该点已不在队列中,在入队操作后要添加标记。
关于松弛操作不太清楚的建议看一看算法书,或者根据给出的思路结合下面的代码再仔细思考一下。原理就是,依次枚举从u出发的边 u —> v,边的长度为len(题目中为l,这里为了看的清楚先写成len),判断d [ u ] + len 是否小于 d [ v ],如果小于则改进 d [ v ] 。
另外提一下的是,SPFA无法处理带负环的图,如果某个点进入队列的次数超过 n 次则存在负环。
算法设计:import java.io.*;
import java.util.*;

class Main {
static int n, m; // n 个顶点,m 条边
static int[] u; // 始点
static int[] v; // 终点
static int[] l; // u 到 v 有一条长度为l的边
static int[] d; // d[i]表示始点到 i 的最短路
static int[] first; // 记录前驱
static int[] next; // 赋值为-1,作为循环终止
static boolean[] vis; // 标记状态
static Queue<Integer> q = new LinkedList<Integer>(); //队列

public static void main(String[] args) throws IOException {
int i;
BufferedReader bfr = new BufferedReader(new InputStreamReader(System.in)); //这里如果用scanner会运行超时
String str = bfr.readLine();
String[] s = str.split(" ");
n = Integer.parseInt(s[0]);
m = Integer.parseInt(s[1]);

u = new int[m + 1];
v = new int[m + 1];
l = new int[m + 1];
first = new int[n + 1];
next = new int[m + 1];
d = new int[n + 1];
vis = new boolean[n + 1];
for (i = 1; i <= n; i++) {
first[i] = -1; // 前驱全部赋值为-1,表示还没有知道前驱
}
for (i = 1; i <= m; i++) {
str = bfr.readLine();
s = str.split(" ");
u[i] = Integer.parseInt(s[0]);
v[i] = Integer.parseInt(s[1]);
l[i] = Integer.parseInt(s[2]);
next[i] = first[u[i]]; // next[] 全部赋值为-1
first[u[i]] = i; //存放 n 个结点
}

spfa(1);
for (i = 2; i <= n; i++)
System.out.println(d[i]);
}

public static void spfa(int s) {
int i, x;
for (i = 2; i <= n; i++) {
d[i] = Integer.MAX_VALUE; // 除始点外,赋值为无穷大
}
q.offer(s); // 添加队头结点
while (!q.isEmpty()) { // 操作直到队空为止
x = q.poll(); // 读取队头结点s,赋值给x,并将s出队
vis[x] = false; // 清除标记,表示不在队列中
for (i = first[x]; i != -1; i = next[i]) {
// 对相邻的点做松弛操作
if (d[v[i]] > d[x] + l[i]) {
d[v[i]] = d[x] + l[i];
if (!vis[v[i]]) {
vis[v[i]] = true; // 标记,表示在队列中
q.offer(v[i]);// 入队
}
}
}
}
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: