dijkstra算法-非负权值求最短路
java
import java.util.Arrays;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int m = scanner.nextInt();
int[][] grid = new int[n + 1][n + 1];
for (int i = 0; i <= n; i++) {
Arrays.fill(grid[i], Integer.MAX_VALUE);
}
for (int i = 0; i < m; i++) {
int p1 = scanner.nextInt();
int p2 = scanner.nextInt();
int val = scanner.nextInt();
grid[p1][p2] = val;
}
int start = 1;
int end = n;
// 存储从源点到每个节点的最短距离
int[] minDist = new int[n + 1];
Arrays.fill(minDist, Integer.MAX_VALUE);
// 记录顶点是否被访问过
boolean[] visited = new boolean[n + 1];
minDist[start] = 0; // 起始点到自身的距离为0
for (int i = 1; i <= n; i++) { // 遍历所有节点
int minVal = Integer.MAX_VALUE;
int cur = 1;
// 1、选距离源点最近且未访问过的节点
for (int v = 1; v <= n; ++v) {
if (!visited[v] && minDist[v] < minVal) {
minVal = minDist[v];
cur = v;
}
}
visited[cur] = true; // 2、标记该节点已被访问
// 3、第三步,更新非访问节点到源点的距离(即更新minDist数组)
for (int v = 1; v <= n; v++) {
if (!visited[v] && grid[cur][v] != Integer.MAX_VALUE && minDist[cur] + grid[cur][v] < minDist[v]) {
minDist[v] = minDist[cur] + grid[cur][v];
}
}
}
if (minDist[end] == Integer.MAX_VALUE) {
System.out.println(-1); // 不能到达终点
} else {
System.out.println(minDist[end]); // 到达终点最短路径
}
}
}
假如有n个节点,要找出到达目的节点经过n-1条路径,需要松弛n-1次。
松弛过程如下:要从1到4,三条边
第一次松弛

其他节点对应的minDist初始化为max,因为我们要求最小距离,那么还没有计算过的节点 默认是一个最大数,这样才能更新最小距离。
当我们开始对所有边开始第一次松弛:
边:节点1 -> 节点2,权值为-1 ,minDist[2] > minDist[1] + (-1),更新 minDist[2] = minDist[1] + (-1) = 0 - 1 = -1 ,如图:

边:节点2 -> 节点3,权值为1 ,minDist[3] > minDist[2] + 1 ,更新 minDist[3] = minDist[2] + 1 = -1 + 1 = 0 ,如图:

边:节点3 -> 节点1,权值为-1 ,minDist[1] > minDist[3] + (-1),更新 minDist[1] = 0 + (-1) = -1 ,如图:

边:节点3 -> 节点4,权值为1 ,minDist[4] > minDist[3] + 1,更新 minDist[4] = 0 + 1 = 1 ,如图:

以上是对所有边进行的第一次松弛,最后 minDist数组为 :-1 -1 0 1 ,(从下标1算起)
所有边进行的第二次松弛,minDist数组为 : -2 -2 -1 0 所有边进行的第三次松弛,minDist数组为 : -3 -3 -2 -1 所有边进行的第四次松弛,minDist数组为 : -4 -4 -3 -2 (本示例中k为3,所以松弛4次)
最后计算的结果minDist[4] = -2,即 起点到 节点4,最多经过 3 个节点的最短距离是 -2,但 正确的结果应该是 1,即路径:节点1 -> 节点2 -> 节点3 -> 节点4。
理论上来说,对所有边松弛一次,相当于计算 起点到达 与起点一条边相连的节点 的最短距离。
对所有边松弛两次,相当于计算 起点到达 与起点两条边相连的节点的最短距离。
对路径权重进行排序后的松弛
边:节点3 -> 节点1,权值为-1 ,节点3还没有被计算过,节点1 不更新。
边:节点3 -> 节点4,权值为1 ,节点3还没有被计算过,节点4 不更新。
边:节点2 -> 节点3,权值为 1 ,节点2还没有被计算过,节点3 不更新。
边:节点1 -> 节点2,权值为 -1 ,minDist[2] > minDist[1] + (-1),更新 minDist[2] = 0 + (-1) = -1 ,如图:

基于负权值的最优路径
简单单源有向图-Bellman_ford
java
import java.util.*;
public class Main {
// 基于Bellman_for一般解法解决单源最短路径问题
// Define an inner class Edge
static class Edge {
int from;
int to;
int val;
public Edge(int from, int to, int val) {
this.from = from;
this.to = to;
this.val = val;
}
}
public static void main(String[] args) {
// Input processing
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int m = sc.nextInt();
List<Edge> graph = new ArrayList<>();
for (int i = 0; i < m; i++) {
int from = sc.nextInt();
int to = sc.nextInt();
int val = sc.nextInt();
graph.add(new Edge(from, to, val));
}
int src = sc.nextInt();
int dst = sc.nextInt();
int k = sc.nextInt();
int[] minDist = new int[n + 1];
int[] minDistCopy;
Arrays.fill(minDist, Integer.MAX_VALUE);
minDist[src] = 0;
for (int i = 0; i < k + 1; i++) { // Relax all edges k + 1 times
minDistCopy = Arrays.copyOf(minDist, n + 1);
for (Edge edge : graph) {
int from = edge.from;
int to = edge.to;
int val = edge.val;
// Use minDistCopy to calculate minDist
if (minDistCopy[from] != Integer.MAX_VALUE && minDist[to] > minDistCopy[from] + val) {
minDist[to] = minDistCopy[from] + val;
}
}
}
// Output printing
if (minDist[dst] == Integer.MAX_VALUE) {
System.out.println("unreachable");
} else {
System.out.println(minDist[dst]);
}
}
}
含有负权回路的最短路径-SPFA
java
class Edge {
public int u; // 边的端点1
public int v; // 边的端点2
public int val; // 边的权值
public Edge() {
}
public Edge(int u, int v) {
this.u = u;
this.v = v;
this.val = 0;
}
public Edge(int u, int v, int val) {
this.u = u;
this.v = v;
this.val = val;
}
}
/**
* SPFA算法(版本3):处理含【负权回路】的有向图的最短路径问题
* bellman_ford(版本3) 的队列优化算法版本
* 限定起点、终点、至多途径k个节点
*/
public class SPFAForSSSP {
/**
* SPFA算法
*
* @param n 节点个数[1,n]
* @param graph 邻接表
* @param startIdx 开始节点(源点)
*/
public static int[] spfa(int n, List<List<Edge>> graph, int startIdx, int k) {
// 定义最大范围
int maxVal = Integer.MAX_VALUE;
// minDist[i] 源点到节点i的最短距离
int[] minDist = new int[n + 1]; // 有效节点编号范围:[1,n]
Arrays.fill(minDist, maxVal); // 初始化为maxVal
minDist[startIdx] = 0; // 设置源点到源点的最短路径为0
// 定义queue记录每一次松弛更新的节点
Queue<Integer> queue = new LinkedList<>();
queue.offer(startIdx); // 初始化:源点开始(queue和minDist的更新是同步的)
// SPFA算法核心:只对上一次松弛的时候更新过的节点关联的边进行松弛操作
while (k + 1 > 0 && !queue.isEmpty()) { // 限定松弛 k+1 次
int curSize = queue.size(); // 记录当前队列节点个数(上一次松弛更新的节点个数,用作分层统计)
while (curSize-- > 0) { //分层控制,限定本次松弛只针对上一次松弛更新的节点,不对新增的节点做处理
// 记录当前minDist状态,作为本次松弛的基础
int[] minDist_copy = Arrays.copyOfRange(minDist, 0, minDist.length);
// 取出节点
int cur = queue.poll();
// 获取cur节点关联的边,进行松弛操作
List<Edge> relateEdges = graph.get(cur);
for (Edge edge : relateEdges) {
int u = edge.u; // 与`cur`对照
int v = edge.v;
int weight = edge.val;
if (minDist_copy[u] + weight < minDist[v]) {
minDist[v] = minDist_copy[u] + weight; // 更新
// 队列同步更新(此处有一个针对队列的优化:就是如果已经存在于队列的元素不需要重复添加)
if (!queue.contains(v)) {
queue.offer(v); // 与minDist[i]同步更新,将本次更新的节点加入队列,用做下一个松弛的参考基础
}
}
}
}
// 当次松弛结束,次数-1
k--;
}
// 返回minDist
return minDist;
}
public static void main(String[] args) {
// 输入控制
Scanner sc = new Scanner(System.in);
System.out.println("1.输入N个节点、M条边(u v weight)");
int n = sc.nextInt();
int m = sc.nextInt();
System.out.println("2.输入M条边");
List<List<Edge>> graph = new ArrayList<>(); // 构建邻接表
for (int i = 0; i <= n; i++) {
graph.add(new ArrayList<>());
}
while (m-- > 0) {
int u = sc.nextInt();
int v = sc.nextInt();
int weight = sc.nextInt();
graph.get(u).add(new Edge(u, v, weight));
}
System.out.println("3.输入src dst k(起点、终点、至多途径k个点)");
int src = sc.nextInt();
int dst = sc.nextInt();
int k = sc.nextInt();
// 调用算法
int[] minDist = SPFAForSSSP.spfa(n, graph, src, k);
// 校验起点->终点
if (minDist[dst] == Integer.MAX_VALUE) {
System.out.println("unreachable");
} else {
System.out.println("最短路径:" + minDist[n]);
}
}
}
双向相同权值多个源节点的目的节点-Floyd算法
java
public class FloydBase {
// public static int MAX_VAL = Integer.MAX_VALUE;
public static int MAX_VAL = 10005; // 边的最大距离是10^4(不选用Integer.MAX_VALUE是为了避免相加导致数值溢出)
public static void main(String[] args) {
// 输入控制
Scanner sc = new Scanner(System.in);
System.out.println("1.输入N M");
int n = sc.nextInt();
int m = sc.nextInt();
System.out.println("2.输入M条边");
// ① dp定义(grid[i][j][k] 节点i到节点j 可能经过节点K(k∈[1,n]))的最短路径
int[][][] grid = new int[n + 1][n + 1][n + 1];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
for (int k = 0; k <= n; k++) {
grid[i][j][k] = grid[j][i][k] = MAX_VAL; // 其余设置为最大值
}
}
}
// ② dp 推导:grid[i][j][k] = min{grid[i][k][k-1] + grid[k][j][k-1], grid[i][j][k-1]}
while (m-- > 0) {
int u = sc.nextInt();
int v = sc.nextInt();
int weight = sc.nextInt();
grid[u][v][0] = grid[v][u][0] = weight; // 初始化(处理k=0的情况) ③ dp初始化
}
// ④ dp推导:floyd 推导
for (int k = 1; k <= n; k++) {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
grid[i][j][k] = Math.min(grid[i][k][k - 1] + grid[k][j][k - 1], grid[i][j][k - 1]);
}
}
}
System.out.println("3.输入[起点-终点]计划个数");
int x = sc.nextInt();
System.out.println("4.输入每个起点src 终点dst");
while (x-- > 0) {
int src = sc.nextInt();
int dst = sc.nextInt();
// 根据floyd推导结果输出计划路径的最小距离
if (grid[src][dst][n] == MAX_VAL) {
System.out.println("-1");
} else {
System.out.println(grid[src][dst][n]);
}
}
}
}
