题目
资源限制
时间限制:1.0s 内存限制:256.0MB
问题描述
给定一个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,保证从任意顶点都能到达其他所有顶点。
解析
根据题目描述,单源最短路径问题,Floyd 算法容易超时,Dijkstra 算法只适用权重为正的图,所以这里采用了更具有普适性的 Bellman-ford 算法。
Bellman-ford 算法适用于单源点最短路径的问题,且可以处理边的权重为负的图。通过松弛函数计算源点到其他所有点的最短距离,缺点是时间复杂度较高,有待优化,且不能处理负环。
可以阅读司徒正美大佬写的博客 Bellman-ford 算法
算法步骤:
- 初始化所有点。每个点保存一个值,表示从源点到达该点的距离,源点值设为0,其他的值设为无穷大即代表不可达。
- 开始循环。循环下标从1到 n-1(n为图中点的个数),循环体遍历所有的边,进行松弛计算。
- 遍历图中所有的边(edge(u, v)),判断是否存在:如果 d(v) > d(u) + w(u, v),则返回 false,表示图中存在从源点可达的权为负的回路,即负权环。
拓展:关于对 Bellman-ford 算法的优化,请参见 SPFA 算法,大家自行查阅资料。
代码
// 试题 算法训练 最短路
import java.util.Scanner;
public class Main {
// 保存结果,单源点到其他点的最短路径
public static long[] distance;
// 声明一个内部类,保存边的信息
static class edge {
public int a; // 起点
public int b; // 终点
public int weight; // 权重
edge(int a, int b, int weight) {
this.a = a;
this.b = b;
this.weight = weight;
}
}
// 求源点到其他所有顶点之间的最短距离
public static void bellmanFord(int n, edge[] E) {
distance = new long[n+1];
// 1. 初始化。这里害怕下标搞错了,将源点下标规定为1,其他初始化为 Integer.MAX_VALUE
for (int i=2; i<=n; i++) {
distance[i] = Integer.MAX_VALUE;
}
// 2. 松弛操作。
for (int i=0; i<n-1; i++) {
boolean flag = false;
for (int j=0; j<E.length; j++) {
// System.out.println("当前E[j]:" + E[j].a + " " + E[j].b + " " + E[i].weight);
if (distance[E[j].a] != Integer.MAX_VALUE && distance[E[j].b] > distance[E[j].a] + E[j].weight) {
distance[E[j].b] = distance[E[j].a] + E[j].weight;
// System.out.println("更新distance[]:" + distance[E[j].a] + " " + distance[E[j].b] + " " + E[j].weight);
flag = true;
}
}
// 若遍历了所有边后,distance 没有更新,则提前退出循环结束算法
if (!flag) {
break;
}
}
// 3. 判定是否存在负环。若遍历了所有边后,distance 还存在更新,则说明有负环。这里因为题目做了约束保证没有负环,害怕卡时间就去掉了
// boolean judge = true;
// for (int i=2; i<=n; i++) {
// if (distance[E[i].b] > distance[E[i].a] + E[i].weight) {
// judge = false;
// break;
// }
// }
// return judge;
}
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int n = input.nextInt();
int m = input.nextInt();
edge[] E = new edge[m];
for (int i=0; i<m; i++) {
int u = input.nextInt();
int v = input.nextInt();
int weight = input.nextInt();
E[i] = new edge(u, v, weight);
}
bellmanFord(n, E);
for (int i=2; i<distance.length; i++) {
System.out.println(distance[i]);
}
input.close();
}
}
差不多也是超时边缘了哈,有时间可以用SPFA 算法试试。
如果我的博客对你有用的话,请给一个一键三连吧!