##问题描述
Dijkstra算法是处理单源最短路径的有效算法,但它局限于边的权值非负的情况,若图中出现权值为负的边,Dijkstra算法就会失效,求出的最短路径就可能是错的。这时候,就需要使用其他的算法来求解最短路径,Bellman-Ford算法就是其中最常用的一个。该算法由美国数学家理查德•贝尔曼 (Richard Bellman, 动态规划的提出者) 和小莱斯特•福特 (Lester Ford) 发明。
##算法分析
首先介绍一下松弛计算:
松弛计算之前如图(a),点B的值是8,但是点A的值加上边上的权重2,得到5,比点B的值小,所以,点B的值减小为5。这个过程的意义是,找到了一条通向B点更短的路线,且该路线是先经过点A,然后通过权重为2的边,到达点B。
如果出现情况(b)则不会修改B的值,因为3+6>8。
- 给定图G(V, E) (其中V、E (Edge) 分别为图G的顶点集与边集),源点s,数组 d(i) (Distant) 记录从源点 s 到顶点 i 的路径长度,初始化 d(n)=∞ 表示不可达,d(s)=0;
- 执行循环,在循环内部,遍历所有的边,进行松弛计算:
- 对于每一条边e(u, v),如果 d(u)+w(u, v) < d(v),则令d(v)=d(u)+w(u, v),w(u, v) 为边 e(u,v) 的权值;
- 若上述操作没有对 d 进行更新,说明最短路径已经查找完毕或者部分点不可达,跳出循环。否则执行下次循环;
- 遍历途中所有的边 e(u, v),判断是否存在 d(u)+w(u, v) < d(v) 的情况,如果有返回false,否则数组 d(n) 中记录的就是源点s到各顶点的最短路径长度。
之所以需要第三部分,是因为如果存在从源点可达的权为负的回路,则应为无法收敛而导致不能求出最短路径。考虑下面过程,图中存在一条负回路:
经过第一次遍历后如图(b),B的值变为5,C变为8,注意权重为-10的边,它的存在导致A的值变为-2,此时A到B的边有5>5+(-2)。
我之前有一个疑问,为什么情况三说明有一条负回路,而不是有一条负边,现在解决了:C的值8来着5+3,如果CA边的绝对值<8,那么A的值仍为0。假设为CA=-3,因为8+(-3)=5>0(A),所以不会改变A,写出来似乎有点蠢的样子…
第二次遍历后如图©,B的值变为3,C变为6,A变为-4。正是因为有一条负边在回路中,导致每次遍历后,各个点的值不断变小,因此无法收敛。
因为第二部分循环的次数是定长的,而且正常情况下能保证d(v)<=d(u)+w(u, v),所以如果存在无法收敛的情况,则肯定能够在第三部分中检查出来。
代码如下:
package practice4;
import java.util.Scanner;
/**
* Created by Y on 2017/5/10.
*/
public class BellmanFord {
private int[] distant;//源点到每一条边的距离
private Edge[] edge;
class Edge {
int u;//边的起点
int v;//边的终点
int weight;//边的权重
Edge(int u, int v, int weight) {
this.u = u;
this.v = v;
this.weight = weight;
}
}
private void relax(int u, int v, int weight) {
if (distant[v] > distant[u] + weight) {
distant[v] = distant[u] + weight;
}
}
private void bellmanFord(int nodeNum) {
distant = new int[nodeNum];
//初始化源点到其它顶点之间的距离为无穷大
for (int i = 1; i < nodeNum; i++) {
distant[i] = Integer.MAX_VALUE;
}
distant[0] = 0;
//进行(nodeNum - 1)次遍历
for (int i = 1; i < nodeNum; i++) {
for (Edge anEdge : edge) {
relax(anEdge.u, anEdge.v, anEdge.weight);
}
}
//判断是否有负回路:存在经过(nodeNum - 1)次遍历后仍可以松弛的边
boolean flag = true;
for (Edge anEdge : edge) {
if (distant[anEdge.v] > distant[anEdge.u] + anEdge.weight) {
flag = false;
break;
}
}
//打印结果
if (flag) {
System.out.println("没有负环");
for (int i = 0; i < nodeNum; i ++) {
System.out.print(distant[i] + " ");
}
} else {
System.out.println("存在负回路,没有最短距离");
}
}
public static void main(String[] args) {
BellmanFord b = new BellmanFord();
Scanner in = new Scanner(System.in);
System.out.println("请输入一个图的顶点总数n和边总数p:");
int nodeNum = in.nextInt();
int edgeNum = in.nextInt();
b.edge = new Edge[edgeNum];
System.out.println("请输入具体边的数据:");
for (int i = 0; i < edgeNum; i++) {
int u = in.nextInt();
int v = in.nextInt();
int weight = in.nextInt();
b.edge[i] = b.new Edge(u, v, weight);
}
b.bellmanFord(nodeNum);
}
}