前言
本文内容参考《啊哈,算法》,算是前一个版本的后续。
实现功能
对不带环的图,不论是否存在负权,都可以寻找到其最短路径。
输出实际最短路径。
中文版参考
/**
* 队列优化Bellman-Ford最短路径算法
*
* 假设5个顶点7条边
* Bellman-Ford最短路径算法的核心代是对7条边进行4次的遍历
* 它不管你当前是否已经是最短路径
* 也不管这条边是否真的需要进行判断
* 都进行判断
* 所以有O(n*m)的时间复杂度
*
* 队列优化的想法是使用队列存储需要判断的顶点,只对需要判断的顶点的边进行判断
* 假设
5 7
1 2 2
1 5 10
2 3 3
2 5 7
3 4 4
4 5 5
5 3 6
* Bellman-Ford对7条边进行判断,更新路径长度
* 第二次还是七条边判断,更新路径长度
*
* 优化的算法是
* 使用一个队列queue维护需要判断的顶点
* 每次只判断该顶点所有的边
* 过程如下
* 初始
* dis存储顶点1到其他顶点的最短距离
* dis{0, Inf, Inf, Inf, Inf},表示顶点1为起点
* 顶点1入队
* 标记顶点1已经在队列内 mark[1] = true;
* 循环开始
* 读取队列头,发现队列头是顶点1,遍历顶点1所有的边。
* 1 2 2
* 判断 dis[2] > dis[1] + 2 成立,所以dis[2] = dis[1] + 2 = 2;(此处乃初版Bellman-Ford的核心)
* dis{0, 2, Inf, Inf, Inf}
* 同时,判断2是否在队列中,当前不在队列中
* 所以将2入队。因为dis[2]有所改变,说明顶点2到其他路径的最短距离也可以发生改变
* 同时标记mark[2] = true;
*
* 1 5 10
* 判断 dis[5] > dis[1] + 10 成立,所以dis[5] = dis[1] + 10 = 10;
* dis{0, 2, Inf, Inf, 10}
* 同理,判断5是否在队列中,当前不存在,所以将5入队。
* 标记mark[5] = true;
*
* 现在队列头所有的边已经处理完毕,将其出队。
* 即顶点1出队,同时去除标记mark[1] = false;
* 循环结果
* 再次循环
* 此时刚刚入队的顶点2成为队列头。
* 读取队列头,发现队列头是顶点2,遍历顶点2所有的边。
* 2 3 3
* 判断 dis[3] > dis[2] + 3 成立,所以dis[3] = dis[2] + 3 = 5;
* dis{0, 2, 5, Inf, 10}
* 同理,判断3是否在队列中,当前不存在,所以将3入队。
* 标记mark[3] = true;
*
* 2 5 7
* 判断 dis[5] > dis[2] + 7 成立,所以dis[5] = dis[2] + 7 = 9;
* dis{0, 2, 5, Inf, 9}
* 同理,判断5是否在队列中
* 5已经在队列中,因为mark[5]=true,所以5不需要再次入队
*
* 顶点2也处理完毕了,将其出队,同时取消标记
* mark[2] = false;
* 循环结束
* 此时顶点5就成了队列头了,继续和上面一样的操作
* ·
* ·
* ·
* 如果存在顶点i发生改变,同时i不在队列中时,就将i入队。
* 一直到队列为空,即后续没有顶点改变了。
* 此时最短路径搜索结束。
*/
代码实现
import java.util.Scanner;
public class OptimumBellmanFord {
static int[] fromVertex, toVertex, weightVertex, first, next, dis, queue, path;
static boolean[] mark;
static int Inf, head, tail;
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n, m;//顶点个数,边数
n = in.nextInt();
m = in.nextInt();
init(n, m);
for (int i = 0; i < m; i++) {
fromVertex[i] = in.nextInt();
toVertex[i] = in.nextInt();
weightVertex[i] = in.nextInt();
//邻接表存储
next[i] = first[fromVertex[i] - 1];
first[fromVertex[i] - 1] = i;
}
int k;
while (head < tail) {
k = first[queue[head] - 1];
while (k != -1) {
if (dis[toVertex[k] - 1] > dis[fromVertex[k] - 1] + weightVertex[k]) {
dis[toVertex[k] - 1] = dis[fromVertex[k] - 1] + weightVertex[k];
path[toVertex[k] - 1] = fromVertex[k];
if (!mark[toVertex[k] - 1]) {
queue[tail++] = toVertex[k];
mark[toVertex[k] - 1] = true;
}
}
k = next[k];
}
mark[queue[head] - 1] = false;
head++;
}
for (int i = 0; i < n; i++) {
showPath(path, 1, i + 1);
System.out.println("距离:" + dis[i]);
}
}
public static void init(int n, int m) {
Inf = 99;
queue = new int[n * m];
head = tail = 0;
fromVertex = new int[m];
toVertex = new int[m];
weightVertex = new int[m];
first = new int[n];
next = new int[m];
//用于检测顶点是否在队列内
mark = new boolean[n];
dis = new int[n];
path = new int[n];
//当前顶点1入队
queue[tail++] = 1;
mark[0] = true;
//初始化数组
for (int i = 0; i < n; i++) {
dis[i] = i * Inf;
first[i] = -1;
}
//初始化next数组
for (int i = 0; i < m; i++) {
next[i] = -1;
}
}
public static void showPath(int[] path, int from, int to) {
if (from == to) {
System.out.print(to + " ");
return;
}
int pre = path[to - 1];
showPath(path, from, pre);
System.out.print(" " + to + " ");
}
}
结果
输入:
5 7
1 2 2
1 5 10
2 3 3
2 5 7
3 4 4
4 5 5
5 3 6
输出:
1 距离:0
1 2 距离:2
1 2 3 距离:5
1 2 3 4 距离:9
1 2 5 距离:9
一点收获
再次感受到写中文版参考的好处,之前学的算法没几天会忘掉,今天要再次写,会想起写中文参考时的思路,就能想出来,而不会感觉这玩意很“新鲜”。