news 2026/4/16 12:04:36

图论问题-最短路径

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
图论问题-最短路径
dijkstra算法-非负权值求最短路
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
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
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算法
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]); } } } }

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/13 12:18:22

计算机Java毕设实战-基于SpringBoot+Vue的智能驿站系统设计与实现基于Java Web的校园菜鸟驿站管理系统【完整源码+LW+部署说明+演示视频,全bao一条龙等】

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/4/15 6:02:31

动态删除表外键依赖

这是一个用于 Liquibase 的 SQL 脚本&#xff0c;它的核心功能是动态查找并删除指向某个特定表字段的所有外键约束。它通常用在数据库重构中&#xff0c;当你需要删除一个有外键引用的表或字段时&#xff0c;必须先解除这些依赖。 下面我将对脚本进行逐行详解&#xff0c;并举例…

作者头像 李华
网站建设 2026/4/13 23:05:37

openFuyao 容器平台快速入门:Nginx 应用部署全流程实操

这里写目录标题一、引言“核心扩展”轻量化设计&#xff0c;从基础编排到异构算力调度可插拔架构&#xff1a;自由定义您的容器平台二、环境准备与安装部署测试环境准备&#xff08;一&#xff09;前提条件确认&#xff08;二&#xff09;版本下载与安装脚本获取&#xff08;三…

作者头像 李华
网站建设 2026/4/4 14:33:17

警惕Vibe Coding ,Agentic Coding认知升级与实践避坑指南

首先需要说明的一点是&#xff0c;我本身不认为自己是 AI 编程的资深专家&#xff0c;所谓的实践完全是基于自己使用了多款 AI 编程产品的切身感受&#xff0c;以及跟 Qoder 研发同学、其他互联网公司 AI IDE 研发同学的交流&#xff0c;如果分享中的观点或者认知有跟你违背的地…

作者头像 李华
网站建设 2026/4/8 14:14:15

Qoder 实战:AI 驱动的研发效率与质量提升

大家好&#xff0c;我是迎天下网络科技有限公司的技术负责人李芳。作为一名一线的 Java 后端开发工程师&#xff0c;今天想和大家分享一下我在实际项目中使用 Qoder 的一些经验。通过几个真实的小案例&#xff0c;我会展示 Qoder 是如何帮助我们提升开发效率、优化代码质量的。…

作者头像 李华
网站建设 2026/4/11 21:04:03

国产期刊被EI收录!首个影响因子12分,录用率67%,国人友好~

Carbon Neutralization《碳中和》近日正式被国际权威数据库EI Compendex收录。该刊2022年创刊&#xff0c;每年出版6期&#xff0c;由温州大学与Wiley联合出版&#xff0c;集高影响因子、高录用率、对国人友好、出版速度快等优势于一身&#xff0c;具有高起点、高包容性和高亲和…

作者头像 李华