图论问题-最短路径

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]);
            }
        }
    }
}
相关推荐
高洁016 小时前
智能体大模型时代的AI革新者
人工智能·深度学习·算法·机器学习·django
mit6.8246 小时前
回溯
算法
鲨莎分不晴6 小时前
强化学习第四课 —— 从“粗糙草稿”到“第一性原理”:为 REINFORCE 算法正名
算法
一起养小猫6 小时前
《Java数据结构与算法》第四篇(一)Java.util包中的树结构实现详解
java·开发语言·数据结构
CoovallyAIHub6 小时前
震后如何快速评估上万栋建筑?俄亥俄州立大学提出混合智能检测方案
深度学习·算法·计算机视觉
Voyager_46 小时前
算法学习记录16——Floyd 判圈算法(环形链表 II)
学习·算法·链表
代码游侠6 小时前
学习笔记——进程控制函数
linux·运维·笔记·学习·算法
小O的算法实验室6 小时前
2022年CIE SCI2区TOP,双向交替搜索 A* 算法的移动机器人全局路径规划,深度解析+性能实测
算法·论文复现·智能算法·智能算法改进
木头左6 小时前
多任务联合训练框架下的遗忘门协同优化趋势跟踪与均值回归双目标平衡
算法·均值算法·回归