前言
之前尝试过Dijkstra算法,用起来不错,想输出路径特别方便,但是有个问题,它不能解决负权边。
而今天的Bellman-Ford算法,就可以解决此问题。
实现功能
对带有负权的图求出两点之间的最短距离
输出实际路径
中文版参考
/**
* Bellman-Ford算法,可解负权边
* 这个算法既有Floyd-Warshall算法的影子,又有Dijkstra算法的味道。
*
* 其思想是遍历寻找其他顶点距离首个指定顶点的距离,暂时不能到达的为无穷大。
* 然后引入了一个中转的边,查看通过这个中转的边能否让两点距离变短。
* 再进行遍历寻找顶点1到其他顶点的最短距离,因为经过上面的一次遍历,已经有值改变了
* 此时会找出其他的点的最短路径,更新之
* 至多重复遍历n-1次即可完成最短路径的寻找
*
* 判断中转是否比较短的方法是
* 起点到目标顶点的距离 = Math.min(起点到目标顶点的[直接]距离, 起点经过其他顶点到达目标顶点的距离)
*
* 一般这样描述都很难懂,上实战
*
* 例如
* 起 终 权
* 2 3 2
* 1 2 -3
* 1 5 5
* 4 5 2
* 3 4 3
*
* 5个顶点
* 5条边
*
* 使用三个一维数组分别存储顶点起点、顶点终点、两个点之间的权值
* fromVertex toVertex weightVertex
* 使用一个dis数组,存储的是第一个起点,到达其他顶点的最短距离
* 初始为dis{0, Inf, Inf, Inf, Inf}
* 处理第一条边
* 2 3 2
* 终点顶点是3,查看首个顶点到达顶点3的目前估计最短距离是: dis[toVertex[1]] = dis[3] = Inf
* 起点顶点时2,查看首个顶点到达顶点2的目前估计最短距离是: dis[fromVertex[1]] = dis[2] = Inf
* 顶点2到顶点3的权值是2
*
* 我们需要查看的是
* A:首个顶点到达顶点3的目前估计最短距离dis[3]
* B:首个顶点到达顶点2的目前估计最短距离dis[2] 加上 顶点2到顶点3的权值2
* 两种情况
* A 大于 B
* 那么将A的距离更新为B,原因如下:
* A表示的是: 顶点1→顶点3
* B表示的是: 顶点1→顶点2→顶点3
* 可以看出,起点和终点都是相同的,唯一不同的是B有个顶点2中转,使得1到达3变短了
* 所以在起点与重终点不变的情况下,可以更新其距离
* 即dis[3] = dis[2] + 2
* A 不大于 B
* 那么表示从顶点1直接到达顶点3更短,所以不更新dis[3]的最短距离
* 进行下一次探查
* 目前情况: A 不大于 B
*
* 所以开始处理第下一条边
* 1 2 -3
* 终点顶点是2,查看首个顶点到达顶点2的目前估计最短距离是: dis[toVertex[2]] = dis[2] = Inf
* 起点顶点时1,查看首个顶点到达顶点1的目前估计最短距离是: dis[fromVertex[1]] = dis[1] = 0
* 顶点2到顶点3的权值是-3
* A:Inf
* B:0 + (-3)
* 目前情况: A 大于 B
* 所以令A = B
* dis[2] = -3
*
* 处理下一条边
* 1 5 5
* 终点顶点是5,dis[toVertex[3]] = dis[5] = Inf
* 起点顶点时1,dis[fromVertex[3]] = dis[1] = 0
* 1到5的权值是5
* A:Inf
* B:0 + 5
* 因此 A 大于 B
* 所以dis[5] = 5
* ·
* ·
* ·
* 一直到5条边处理完毕,此时的dis是{0 -3 Inf Inf 5}
*
* 现在再重复上面的步骤,再对5条边进行一样的步骤
* 第二次处理第一条边
* 2 3 2
* 终点顶点是3,查看首个顶点到达顶点3的目前估计最短距离是: dis[toVertex[1]] = dis[3] = Inf
* 起点顶点时2,查看首个顶点到达顶点2的目前估计最短距离是: dis[fromVertex[1]] = dis[2] = -3(因为第一次的循环,此处已改变)
* 顶点2到顶点3的权值是2
* A:Inf
* B:-3 + 2 = -1
* 因此 A 大于 B
* 所以dis[3] = -1
* ·
* ·
* 重复至多n-1次即可找出最短路径
*
* 不过在dis数组没有变化时,表示已经寻找完毕,可提前退出循环。
*
* 以上就是Bellman-Ford求最短路径的步骤
*/
代码实现
import java.util.Scanner;
public class BellmanFord {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
//起点,终点,权值,最短距离
int[] fromVertex, toVertex, weightVertex, dis, preNode;
//顶点个数,边
int n, m;
//表无穷大
int Inf = 999;
n = in.nextInt();
m = in.nextInt();
//初始化数据
fromVertex = new int[m];
toVertex = new int[m];
weightVertex = new int[m];
dis = new int[n];
preNode = new int[n];
for (int i = 0; i < m; i++) {
fromVertex[i] = in.nextInt();
toVertex[i] = in.nextInt();
weightVertex[i] = in.nextInt();
}
//初始化dis
dis[0] = 0;
preNode[0] = 1;
for (int i = 1; i < n; i++) {
dis[i] = Inf;
}
boolean flag;
//开始处理边,寻找最短路径
for (int k = 0; k < n - 1; k++) {
flag = true;
for (int i = 0; i < m; i++) {
if (dis[toVertex[i] - 1] > dis[fromVertex[i] - 1] + weightVertex[i]) {
dis[toVertex[i] - 1] = dis[fromVertex[i] - 1] + weightVertex[i];
//记录终点顶点的前一个顶点
//用于输出路径,目标顶点的前一个点到目标顶点一定最短
preNode[toVertex[i] - 1] = fromVertex[i];
flag = false;
//System.out.println("from " + fromVertex[i] + " to " + toVertex[i] + " i=" + (i+1));
//刚开始不明白怎么输出路径,就打印了一下
//System.out.println("pre " + toVertex[i] + " from " + fromVertex[i] + " i=" + (i+1));
}
}
//如果没有更新过最短路径,表示最短路径寻找完毕。
if (flag) {
break;
}
}
//输出顶点1到各个顶点之间的最短路径和距离
for (int i = 0; i < 5; i++) {
showPath(preNode, 1, i+1);
System.out.println(" 距离:" + dis[i]);
}
}
public static void showPath(int[] path, int from, int to) {
int pre;
if (from == to) {
System.out.print(to + " ");
return;
}
pre = path[to - 1];
showPath(path, from, pre);
System.out.print(" " + to + " ");
}
}
结果
输入
5 5
2 3 2
1 2 -3
1 5 3(顶点1到顶点5的权值被我换成了3)
4 5 2
3 4 3
输出
1 距离:0
1 2 距离:-3
1 2 3 距离:-1
1 2 3 4 距离:2
1 5 距离:3
一点瞎扯
利用递归倒序输出路径的思路,很早以前看过,一直没怎么用。
然后自己尝试着写出来了。
很多之前看过觉得很高端的东西,当时写不出来,后来慢慢能写出来了,非常有成就感。
就像快速排序,我很早就知道它,也是前不久才写出来,对于暂时不懂得,不要心急。
出来混的总是要还的,当时学不会的,总可以试着以后补回来。
但是,别忘了。