Bellman-Ford Algorithm
Bellman-Ford主要用来求解存在负权边的图的最短路径,这一点要优于迪杰斯特拉算法,不过BF算法的时间复杂度更高。
我们先来看看松弛(Relaxation)操作:
松弛操作基本思想就是估计的最短路径值渐渐地被更加准确的值替代,直至得到最优解。初始时,我们找到一个原点src,该店除了到自己的距离为0之外,到其他任何点的距离都应设置为正无穷(infinity),这样一来,每条边之间的距离估计值都比真实值要大,我们要做的就是在每次迭代的过程中找到一条新的具有更小长度的路径来替代原路径长度。
比如这样:
这里我们以dist[]表示源点到其他点的最短路径长。
算法其实很简单,用两个for循环就搞定了。第一层for循环需要遍历(节点数-1)次,因为这样才能得到所有可能的最短路径。因为,若图中存在未确认的顶点,则每一次迭代后都会新增加至少一个已确认顶点。那么,要找到从起点出发到各顶点的最短路径权值,极端情况下,图中所有顶点都在一条最短路径上,且松弛边按照最坏情况下进行,即一次只增加一个已确认顶点,则需要执行的迭代次数为(节点数-1)次。
有两个问题注意一下,这也是我在写完代码后都存在的疑问:
1.为什么Bellman-Ford算法是正确的?
这里只找到了比较粗略的回答:
这里还涉及到负权环的概念,所谓负权环(negative weight cycle),就是源点到源点的一个环,环上权重和为负数。那么,如果一个图中存在负权环,那么这个算法在找最短路径时,就会陷入一个环中怎么也找不到(个人拙见,如有不正确欢迎指正)。所以这个算法也可以用来找负权环,这篇文章的代码就是用来找负权环的。
附上一个图:
1->2->3->1就是一个负权环,这条路径长为-2.
现在贴代码啦!
首先我们需要一个边的集合,在Java中,为了方便,我们就写一个内部类表示边。
1 private static class Edge { 2 public int start; 3 public int end; 4 public int weight; 5 6 public Edge(int start, int end, int weight) { 7 this.start = start; 8 this.end = end; 9 this.weight = weight; 10 } 11 }
然后,我们自然需要一个dist[]数组,这个数组直接在构造器里面初始化就行,此时需要传一个参数n,表示节点数。
1 public ShortestPath(int n) { 2 dist = new int[n+1]; 3 }
然后,我们需要对这个数组初始化,那就是,对除了源点自己到自己的距离设为0之外,源点到其他点的距离全设为正无穷。
1 private void init(int n) { 2 for (int i = 1; i < n; ++i) { 3 dist[i] = Integer.MAX_VALUE; 4 } 5 }
接下来就是我们万众瞩目的BF算法啦
1 public boolean Bellman_Ford(int n, Edge[] edgeList) { 2 init(n); 3 //外层for循环是迭代|V|-1次 4 for (int i = 1; i < n; ++i) { 5 //接下来我们进行松弛操作 6 for (int j = 0; j < edgeList.length; ++j) { 7 if (dist[edgeList[j].end] > dist[edgeList[j].start] + edgeList[j].weight) { 8 dist[edgeList[j].end] = dist[edgeList[j].start] + edgeList[j].weight; 9 } 10 } 11 } 12 13 boolean flag = true; 14 for (int i = 0; i < edgeList.length; ++i) { 15 if (dist[edgeList[i].end] > dist[edgeList[i].start] + edgeList[i].weight) { 16 flag = false; 17 break; 18 } 19 } 20 return flag; 21 }
先初始化,再松弛,最后返回判断结果。
完整代码:
1 package com.hw.others; 2 3 import java.util.Scanner; 4 5 public class ShortestPath { 6 private static int[] dist; 7 8 private static class Edge { 9 public int start; 10 public int end; 11 public int weight; 12 13 public Edge(int start, int end, int weight) { 14 this.start = start; 15 this.end = end; 16 this.weight = weight; 17 } 18 } 19 20 public ShortestPath(int n) { 21 dist = new int[n+1]; 22 } 23 24 private void init(int n) { 25 for (int i = 1; i < n; ++i) { 26 dist[i] = Integer.MAX_VALUE; 27 } 28 } 29 30 public boolean Bellman_Ford(int n, Edge[] edgeList) { 31 init(n); 32 //外层for循环是迭代|V|-1次 33 for (int i = 1; i < n; ++i) { 34 //接下来我们进行松弛操作 35 for (int j = 0; j < edgeList.length; ++j) { 36 if (dist[edgeList[j].end] > dist[edgeList[j].start] + edgeList[j].weight) { 37 dist[edgeList[j].end] = dist[edgeList[j].start] + edgeList[j].weight; 38 } 39 } 40 } 41 42 boolean flag = true; 43 for (int i = 0; i < edgeList.length; ++i) { 44 if (dist[edgeList[i].end] > dist[edgeList[i].start] + edgeList[i].weight) { 45 flag = false; 46 break; 47 } 48 } 49 return flag; 50 } 51 52 public static void main(String[] args) { 53 Scanner s = new Scanner(System.in); 54 System.out.println("输入图中点的数量:"); 55 int nodes = s.nextInt(); 56 System.out.println("输入该图中边的数量:"); 57 int edges = s.nextInt(); 58 Edge[] edgeList = new Edge[edges]; 59 System.out.println("输入图中所有的边:"); 60 for (int i = 0; i < edges; ++i) { 61 int start = s.nextInt(); 62 int end = s.nextInt(); 63 int weight = s.nextInt(); 64 Edge edge = new Edge(start, end, weight); 65 edgeList[i] = edge; 66 } 67 ShortestPath ss = new ShortestPath(nodes); 68 if (ss.Bellman_Ford(nodes, edgeList)) { 69 System.out.println("图中没有负权环"); 70 } else { 71 System.out.println("图中有负权环"); 72 } 73 s.close(); 74 } 75 }
运行效果:
我们以这个图为例:
现在输入数据:
完美!