Bellman-Flod算法
对于带有负权值的图,我们已经不能通过Dijkstra算法进行求解了
原因:Dijkstra每次都会找一个距源点(设为s)最近的点,然后将该距离定为这个点到源点的最短路径;如果一个顶点u被加入了book[i])( book[i] == 1 说明这个s到u的路径权值已被记录,不可再更改),但是如果存在v到u的权值为负,那么s经v到u到值要更小。
例如:
如果用Dijkstra跑只能得到2这个结果,但实际结果应该是1才对
s—->u 2
s—->v—-> 1
为解决带负权值的问题,这里需要用到Bellman-Flod算法
问题描述:有如下带负权值的图,求从1号点到其他各点的最短路径长度
Input:
5 5
2 3 2
1 2 -3
1 5 5
4 5 2
3 4 3
Output:
0 -3 -1 2 4
import java.util.Scanner;
public class minPath {static int[] u = new int[10];static int[] v = new int[10];static int[] w = new int[10];static int[] dis = new int[10];static int n, m;static Scanner input = new Scanner(System.in);public static void main(String[] args) {n = input.nextInt();m = input.nextInt();for (int i = 1; i <= m; i++) {u[i] = input.nextInt();v[i] = input.nextInt();w[i] = input.nextInt();}for (int i = 1; i <= n; i++) {dis[i] = 99999999;}dis[1] = 0;bellmanFlod();for (int i = 1; i <= n; i++) {System.out.print(dis[i] + " ");}}private static void bellmanFlod() {/*** 由离散数学的理论得出,在一个n个顶点的图中,任意两点之间的最短路径最多包含n-1条边* */for (int i = 1; i <= n - 1; i++) {for (int j = 1; j <= m; j++) {if (dis[v[j]] > dis[u[j]] + w[j]) {dis[v[j]] = dis[u[j]] + w[j];}}}}
}
时间复杂度:O(NM)
算法优化
优化版本一
优化原因:在实际操作中,很多时候不会进行n-1次,毕竟不是每个点都会达到n-1条边的情况的,因此当不在能更新之后,我们就可以终止程序了,即使现在还没进行到第n-1阶段。
优化方法:添加一个一维数组备份来dis,如果新一轮的松弛中数组没有发生变化,则可以提前跳出循环。
import java.util.Scanner;public class minPath {static int[] u = new int[10];static int[] v = new int[10];static int[] w = new int[10];static int[] dis = new int[10];static int[] bak = new int[10];static int n, m;static int check, flag;static Scanner input = new Scanner(System.in);public static void main(String[] args) {n = input.nextInt();m = input.nextInt();for (int i = 1; i <= m; i++) {u[i] = input.nextInt();v[i] = input.nextInt();w[i] = input.nextInt();}for (int i = 1; i <= n; i++) {dis[i] = 99999999;}dis[1] = 0;bellmanFlod();if (flag == 1) {System.out.println("此图含有负权回路");} else {for (int j = 1; j <= n; j++) {System.out.print(dis[j] + " ");}}}private static void bellmanFlod() {/*** 由离散数学的理论得出,在一个n个顶点的图中,任意两点之间的最短路径最多包含n-1条边* */for (int i = 1; i <= n - 1; i++) {bak[i] = dis[i];for (int j = 1; j <= m; j++) {if (dis[v[j]] > dis[u[j]] + w[j]) {dis[v[j]] = dis[u[j]] + w[j];}}/*** 检测dis是否还在变化* */check = 0;for (int j = 1; j <= n; j++) {if (bak[i] != dis[i]) {check = 1;break;}if (check == 0) {break;}}/*** 检测负权回路* 原理:最短路径所包含的边做多为n-1条,在进行n-1次松弛之后如果还能继续松弛成功,* 那说明此图必定含有负权回路* */flag = 0;for (int j = 1; j <= m; j++) {if (dis[v[i]] > dis[u[i]] + w[i]) {flag = 1;}}}}
}
优化原因:因为最短路径做多有n-1条边,所以Bellman-Ford算法做多有n-1个阶段。而每个阶段都要对每条边进行“松弛操作”,使得“估计值”变为“确定值”。而对于本阶段来说,上个阶段松弛完的结果是不会再改变的,而原算法每个阶段都会重新计算,浪费时间。
优化方法:每次仅对最短路“估计值”变化为“确定值”的顶点的所有出边执行松弛操作,可以采用队列进行优化
时间复杂度:O(NM)
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;public class minPath {static int[] u = new int[8];static int[] v = new int[8];static int[] w = new int[8];/*** first 要比 n 的值大1* next 要比 m 的值大1* */static int[] first = new int[6];static int[] next = new int[8];static int[] dis = new int[6];static int[] book = new int[6];static int n, m;static Queue<Integer> queue = new LinkedList<>();static Scanner input = new Scanner(System.in);public static void main(String[] args) {n = input.nextInt();m = input.nextInt();for (int i = 1; i <= n; i++) {dis[i] = 99999999;}dis[1] = 0;for (int i = 1; i <= n; i++) {first[i] = -1;}for (int i = 1; i <= m; i++) {u[i] = input.nextInt();v[i] = input.nextInt();w[i] = input.nextInt();/*** 建立邻接表的关键语句!!!* */next[i] = first[u[i]];first[u[i]] = i;}queue.offer(1);book[1] = 1;bellomanFlodPlus();for (int i = 1; i <= n; i++) {System.out.print(dis[i] + " ");}}private static void bellomanFlodPlus() {while (!queue.isEmpty()) {/*** 当前的队首元素* */int k = first[queue.peek()];while (k != -1) {if (dis[v[k]] > dis[u[k]] + w[k]) {dis[v[k]] = dis[u[k]] + w[k];/*** 用数组来判断v[k]是否已经在队列中* 不使用数组进行记录的话,要迭代队列,十分不方便* */if(book[v[k]] == 0) {queue.offer(v[k]);book[v[k]] = 1;}}k = next[k];}book[queue.peek()] = 0;queue.remove();}}
}