代码随想录第六十二天| Floyd 算法精讲 A * 算法精讲 (A star算法) 最短路算法总结篇

Floyd 算法精讲

题目描述

小明希望在公园散步时找到从一个景点到另一个景点的最短路径。给定公园的景点图,包含 N 个景点和 M 条双向道路,每条道路有已知的长度。小明有 Q 个观景计划,每个计划包含一个起点和终点,求每个计划的最短路径长度。

输入包含景点数量 N、道路数量 M,接着 M 行每行三个整数 u、v、w 表示景点 u 和 v 之间的双向道路长度为 w。然后输入观景计划数量 Q,接着 Q 行每行两个整数 start 和 end。输出每个计划的最短路径长度,若无路径则输出 -1。

输入输出示例

输入:

7 3 1 2 4 2 5 6 3 6 8 2 1 2 2 3

输出:

4 -1

解题思路

算法原理

Floyd 算法是一种多源最短路径算法,基于动态规划思想。通过引入中间节点逐步更新所有节点对之间的最短距离,最终得到全局最优解。该算法可以处理带负权的图。

动态规划思想

  1. 状态定义grid[i][j][k] 表示从节点 i 到节点 j,只能经过节点 1 到 k 的最短距离。
  2. 状态转移grid[i][j][k] = min(grid[i][j][k-1], grid[i][k][k-1] + grid[k][j][k-1]),即不经过节点 k 或经过节点 k 的最小值。
  3. 初始化:初始时,grid[i][j][0] 为直接连接的边的权值,其他为无穷大。
  4. 遍历顺序:按 k、i、j 的顺序遍历,确保每一步都基于之前的结果。

空间优化

由于每次计算只依赖前一次的结果,可以将三维数组优化为二维数组,减少空间复杂度。

代码实现

import java.util.Scanner;public class FloydAlgorithm {public static final int MAX_VAL = 10005; // 边的最大距离public static void main(String[] args) {Scanner sc = new Scanner(System.in);int n = sc.nextInt(); // 景点数量int m = sc.nextInt(); // 道路数量// 初始化距离矩阵int[][] grid = new int[n + 1][n + 1];for (int i = 1; i <= n; i++) {for (int j = 1; j <= n; j++) {grid[i][j] = MAX_VAL; // 初始化为最大值}}// 输入道路信息for (int i = 0; i < m; i++) {int u = sc.nextInt();int v = sc.nextInt();int w = sc.nextInt();grid[u][v] = w; // 双向道路grid[v][u] = w;}// Floyd 算法核心部分for (int k = 1; k <= n; k++) {for (int i = 1; i <= n; i++) {for (int j = 1; j <= n; j++) {// 更新 i 到 j 的最短路径if (grid[i][k] + grid[k][j] < grid[i][j]) {grid[i][j] = grid[i][k] + grid[k][j];}}}}// 处理观景计划int q = sc.nextInt(); // 计划数量for (int i = 0; i < q; i++) {int start = sc.nextInt();int end = sc.nextInt();if (grid[start][end] == MAX_VAL) {System.out.println(-1); // 无路径} else {System.out.println(grid[start][end]); // 最短路径长度}}}
}

代码注释

  1. 初始化距离矩阵grid[i][j] 初始化为最大值,表示初始时无路径。
  2. 输入道路信息:读取每条道路并更新距离矩阵,双向道路需两边更新。
  3. Floyd 算法核心:三重循环,逐步引入中间节点 k,更新所有节点对的最短路径。
  4. 处理观景计划:对每个计划查询最短路径,若仍为最大值则输出 -1,否则输出路径长度。

总结

Floyd 算法通过动态规划思想,利用三维数组逐步更新节点间的最短路径。空间优化后使用二维数组,时间复杂度为 O(n^3),适合稠密图或多源最短路径问题。理解动态规划的状态转移和遍历顺序是掌握该算法的关键。

A* 算法精讲

题目描述

在象棋中,骑士(马)按照“日”字形移动。给定骑士的起始坐标和目标坐标,计算从起点到达目标点所需的最短步数。棋盘大小为 1000x1000。

输入包含多个测试用例,每行给出起点和目标点的坐标。输出每个测试用例的最短步数。

输入输出示例

输入:

6
5 2 5 4
1 1 2 2
1 1 8 8
1 1 8 7
2 1 3 3
4 6 4 6

输出:

2
4
6
5
1
0

解题思路

算法原理

A* 算法是一种启发式搜索算法,结合了广度优先搜索(BFS)和迪杰斯特拉算法(Dijkstra)的优点,通过引入启发式函数来指导搜索方向,从而提高搜索效率。

关键点

  • 启发式函数:用于估计当前节点到目标节点的距离,指导搜索方向。常见的启发式函数包括曼哈顿距离、欧氏距离和切比雪夫距离。本题采用欧氏距离。
  • 优先级队列:根据启发式函数计算的值(F = G + H)对节点进行排序,优先扩展最有可能接近目标的节点。
  • 节点状态记录:记录已访问节点的步数,避免重复访问。

代码实现

import java.util.*;
import java.awt.Point;
import java.util.PriorityQueue;public class KnightAttack {// 定义骑士的8个可能移动方向private static final int[][] MOVES = {{-2, -1}, {-2, 1}, {-1, -2}, {-1, 2},{1, -2}, {1, 2}, {2, -1}, {2, 1}};public static void main(String[] args) {Scanner scanner = new Scanner(System.in);int n = scanner.nextInt();for (int i = 0; i < n; i++) {int a1 = scanner.nextInt();int a2 = scanner.nextInt();int b1 = scanner.nextInt();int b2 = scanner.nextInt();System.out.println(astar(new Point(a1, a2), new Point(b1, b2)));}}private static int astar(Point start, Point end) {// 优先级队列,存储 (总代价, 当前位置, 已走步数)PriorityQueue<Node> heap = new PriorityQueue<>((a, b) -> Double.compare(a.f, b.f));heap.add(new Node(start, 0, heuristic(start, end)));// 记录访问过的节点及其步数Map<Point, Integer> visited = new HashMap<>();visited.put(start, 0);while (!heap.isEmpty()) {Node current = heap.poll();// 检查是否到达目标if (current.point.equals(end)) {return current.steps;}// 生成下一个可能的移动位置for (int[] move : MOVES) {Point nextPoint = new Point(current.point.x + move[0], current.point.y + move[1]);// 检查是否在棋盘范围内if (nextPoint.x >= 1 && nextPoint.x <= 1000 && nextPoint.y >= 1 && nextPoint.y <= 1000) {int newSteps = current.steps + 1;// 如果该位置未被访问或有更短路径if (!visited.containsKey(nextPoint) || newSteps < visited.get(nextPoint)) {visited.put(nextPoint, newSteps);double f = newSteps + heuristic(nextPoint, end);heap.add(new Node(nextPoint, newSteps, f));}}}}// 如果无法到达目标return -1;}private static double heuristic(Point start, Point end) {// 欧氏距离启发式函数double dx = start.x - end.x;double dy = start.y - end.y;return Math.sqrt(dx * dx + dy * dy);}private static class Node {Point point;int steps;double f; // 总代价:f = steps + heuristicNode(Point point, int steps, double f) {this.point = point;this.steps = steps;this.f = f;}}
}

代码注释

  1. 移动方向定义:骑士有8个可能的移动方向,用 moves 数组表示。
  2. 启发式函数heuristic 函数计算两个点之间的欧氏距离,作为估计距离。
  3. A 搜索函数*:astar 函数使用优先级队列进行搜索,每次扩展最有可能接近目标的节点。
  4. 优先级队列:队列中存储元组 (总代价, 当前位置x, 当前位置y, 已走步数),按总代价排序。
  5. 访问记录visited 字典记录已访问节点及其最小步数,避免重复访问。
  6. 输入处理:读取多个测试用例,对每个用例调用 astar 函数计算最短步数。

总结

A* 算法通过引入启发式函数,能够在大规模地图上高效地找到最短路径。相比传统的广度优先搜索(BFS),A* 算法通过优先扩展接近目标的节点,减少了不必要的搜索,提高了效率。选择合适的启发式函数对算法性能至关重要。

最短路算法总结

至此已经讲解了四大最短路算法,分别是 Dijkstra、Bellman-Ford、SPFA 和 Floyd。

针对这四大最短路算法,我用了七篇长文才彻底讲清楚,分别是:

  • Dijkstra 朴素版
  • Dijkstra 堆优化版
  • Bellman-Ford
  • Bellman-Ford 队列优化算法(又名 SPFA)
  • Bellman-Ford 算法判断负权回路
  • Bellman-Ford 之单源有限最短路
  • Floyd 算法精讲
  • 启发式搜索:A * 算法

最短路算法比较复杂,而且各自有各自的应用场景。以下是讲过的最短路算法的使用场景对比表:

算法适用场景特点
Dijkstra单源且边为正数效率高,适合稀疏图和稠密图的不同版本
SPFA单源边可为负数一般情况下比 Bellman-Ford 效率高,适合大部分场景
Bellman-Ford单源边可为负数,需判断负权回路可以检测负权回路,代码实现相对简单
Floyd多源点求最短路适合节点数量较少的情况,实现简单

(因为 A * 属于启发式搜索,和上面最短路算法并不是一类,不适合一起对比,所以没有放在一起)

可能有同学感觉:这个表太复杂了,我记也记不住。

其实记不住的原因还是对这几个最短路算法没有深刻的理解。这里给大家一个大体使用场景的分析:

  • 如果遇到单源且边为正数,直接 Dijkstra。至于使用朴素版还是堆优化版,还是取决于图的稠密度。多少节点多少边算是稠密图,多少算是稀疏图,这个没有量化。如果想量化,只能写出两个版本然后做实验去测试,不同的判题机得出的结果还不太一样。一般情况下,可以直接用堆优化版本。
  • 如果遇到单源边可为负数,直接 Bellman-Ford。同样 SPFA 还是 Bellman-Ford 取决于图的稠密度。一般情况下,直接用 SPFA。
  • 如果有负权回路,优先 Bellman-Ford。如果是有限节点最短路,也优先 Bellman-Ford,理由是写代码比较方便。
  • 如果是遇到多源点求最短路,直接 Floyd。除非源点特别少,且边都是正数,那可以多次 Dijkstra 求出最短路径,但这种情况很少。一般出现多个源点了,就是想让你用 Floyd 了。
  • 对于 A *,由于其高效性,在实际工程应用中使用最为广泛。由于其结果的不唯一性,也就是可能是次短路的特性,一般不适合作为算法题。游戏开发、地图导航、数据包路由等都广泛使用 A * 算法。

图论总结

从深搜广搜到并查集,从最小生成树到拓扑排序,最后是最短路算法系列。

至此算上本篇,一共30篇文章,图论之旅就在此收官了。

在0098.所有可达路径,我们接触了两种图的存储方式,邻接表和邻接矩阵,掌握两种图的存储方式很重要。

图的存储方式也是大家习惯在核心代码模式下刷题经常忽略的知识点。因为在力扣上刷题不需要掌握图的存储方式。

深搜与广搜

在二叉树章节中,其实我们讲过了深搜和广搜在二叉树上的搜索过程。

在图论章节中,深搜与广搜就是在图这个数据结构上的搜索过程。

深搜与广搜是图论里基本的搜索方法,大家需要掌握三点:

  • 搜索方式:深搜是一个方向搜,不到黄河不回头。广搜是围绕起点一圈一圈的去搜。
  • 代码模板:需要熟练掌握深搜和广搜的基本写法。
  • 应用场景:图论题目基本上可以用深搜也可用广搜,看哪个方便而已。

注意事项

需要注意的是,同样是深搜模板题,会有两种写法。

在0099.岛屿的数量深搜.md和0105.有向图的完全可达性,涉及到dfs的两种写法。

我们对dfs函数的定义是处理当前节点还是处理下一个节点很重要,决定了两种dfs的写法。

这也是为什么很多录友看到不同的dfs写法,结果发现提交都能过的原因。

而深搜还有细节,有的深搜题目需要用到回溯的过程,有的就不用回溯的过程,

一般是需要计算路径的问题需要回溯,如果只是染色问题(岛屿问题系列)就不需要回溯。

例如:0105.有向图的完全可达性深搜就不需要回溯,而0098.所有可达路径中的递归就需要回溯,文章中都有详细讲解

注意:以上说的是不需要回溯,不是没有回溯,只要有递归就会有回溯,只是我们是否需要用到回溯这个过程,这是需要考虑的。

很多录友写出来的广搜可能超时了,例如题目:0099.岛屿的数量广搜

根本原因是只要加入队列就代表走过,就需要标记,而不是从队列拿出来的时候再去标记走过。

具体原因,我在0099.岛屿的数量广搜中详细讲了。

在深搜与广搜的讲解中,为了防止惯性思维,我特别加入了题目0106.岛屿的周长,提醒大家,看到类似的题目,也不要上来就想着深搜和广搜。

还有一些图的问题,在题目描述中,是没有图的,需要我们自己构建一个图,例如0110.字符串接龙,题目中连线都没有,需要我们自己去思考什么样的两个字符串可以连成线。

并查集

并查集相对来说是比较复杂的数据结构,其实他的代码不长,但想彻底学透并查集,需要从多个维度入手,

我在理论基础篇的时候讲解如下重点:

  • 为什么要用并查集,怎么不用个二维数据,或者set、map之类的。
  • 并查集能解决那些问题,哪些场景会用到并查集
  • 并查集原理以及代码实现
  • 并查集写法的常见误区
  • 带大家去模拟一遍并查集的过程
  • 路径压缩的过程
  • 时间复杂度分析

上面这几个维度大家都去思考了,并查集基本就学明白了。

其实理论基础篇就算是给大家出了一道裸的并查集题目了,所以在后面的题目安排中,会稍稍的拔高一些,重点在于并查集的应用上。

例如并查集可以判断这个图是否是树,因为树的话,只有一个根,符合并查集判断集合的逻辑,题目:0108.冗余连接。

在0109.冗余连接II中对有向树的判断难度更大一些,需要考虑的情况比较多。

最小生成树

最小生成树是所有节点的最小连通子图,即:以最小的成本(边的权值)将图中所有节点链接到一起。

最小生成树算法,有prim和kruskal。

prim算法是维护节点的集合,而Kruskal是维护边的集合。

在稀疏图中,用Kruskal更优。在稠密图中,用prim算法更优。

边数量较少为稀疏图,接近或等于完全图(所有节点皆相连)为稠密图

Prim算法时间复杂度为O(n^2),其中n为节点数量,它的运行效率和图中边树无关,适用稠密图。

Kruskal算法时间复杂度为O(nlogn),其中n为边的数量,适用稀疏图。

关于prim算法,我自创了三部曲,来帮助大家理解:

第一步,选距离生成树最近节点

第二步,最近节点加入生成树

第三步,更新非生成树节点到生成树的距离(即更新minDist数组)

大家只要理解这三部曲,prim算法至少是可以写出一个框架出来,然后在慢慢补充细节,这样不至于自己在写prim的时候两眼一抹黑完全凭感觉去写。

minDist数组是prim算法的灵魂,它帮助prim算法完成最重要的一步,就是如何找到距离最小生成树最近的点。

kruscal的主要思路:

  • 边的权值排序,因为要优先选最小的边加入到生成树里
  • 遍历排序后的边
  • 如果边首尾的两个节点在同一个集合,说明如果连上这条边图中会出现环
  • 如果边首尾的两个节点不在同一个集合,加入到最小生成树,并把两个节点加入同一个集合

而判断节点是否在一个集合以及将两个节点放入同一个集合,正是并查集的擅长所在。

所以Kruskal是需要用到并查集的。

这也是我在代码随想录图论编排上为什么要先讲解并查集在讲解最小生成树。

拓扑排序

拓扑排序是在图上的一种排序。

概括来说,给出一个有向图,把这个有向图转成线性的排序就叫拓扑排序。

同样,拓扑排序也可以检测这个有向图是否有环,即存在循环依赖的情况。

拓扑排序的一些应用场景,例如:大学排课,文件下载依赖等等。

只要记住如下两步拓扑排序的过程,代码就容易写了:

  • 找到入度为0的节点,加入结果集
  • 将该节点从图中移除

最短路算法

最短路算法是图论中,比较复杂的算法,而且不同的最短路算法都有不同的应用场景。

我在最短路算法总结篇里已经做了一个高度的概括。

大家要时常温故而知新,才能透彻理解各个最短路算法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/73377.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何避免测试环境不稳定导致的误报

避免测试环境不稳定导致误报的核心方法包括搭建独立稳定的测试环境、使用环境监控工具、建立环境变更管理机制、定期维护更新测试环境以及提升团队的环境管理意识。 其中&#xff0c;搭建独立稳定的测试环境尤为关键。独立的测试环境能有效隔离其他环境的干扰&#xff0c;保证测…

初级:I/O与NIO面试题深度剖析

一、引言 在Java开发中&#xff0c;I/O&#xff08;输入/输出&#xff09;操作是程序与外部设备&#xff08;如磁盘、网络等&#xff09;进行数据交互的重要方式。传统的I/O模型在处理大规模数据和高并发场景时存在一定的局限性&#xff0c;而NIO&#xff08;New I/O&#xff…

Axure RP9教程 :轮播图(动态面板) | 头部锁定

文章目录 引言I 轮播图操作步骤在画布中添加一个动态面板设置面板状态II 头部锁定将头部区域选中,右键组合或用Ctrl+G快捷键;将组合的头部区域,右键创建动态面板;引言 动态面板的功能十分强大,比如:拥有独立的内部坐标系,有多个状态; Banner的案例中会用到动态面板多个…

超微服务器主板重置ipmi登录密码

超微服务器主板重置ipmi登录密码 超微服务器的ipmi登录密码不对&#xff0c;需要重置但是bios内并没有找到可以设置的选项。 以下是解决办法&#xff1a; 安装IPMITOOL apt install ipmitool -y执行以下命令加载模块&#xff1a; modprobe ipmi_watchdog modprobe ipmi_po…

蓝桥杯第十届 数的分解

题目描述 本题为填空题&#xff0c;只需要算出结果后&#xff0c;在代码中使用输出语句将所填结果输出即可。 把 2019 分解成 3 个各不相同的正整数之和&#xff0c;并且要求每个正整数都不包含数字 2 和 4&#xff0c;一共有多少种不同的分解方法&#xff1f; 注意交换 3 个…

Docker入门篇4:查看容器资源、查看容器详细信息、查看容器日志、查看容器内运行的进程

大家好我是木木&#xff0c;在当今快速发展的云计算与云原生时代&#xff0c;容器化技术蓬勃兴起&#xff0c;Docker 作为实现容器化的主流工具之一&#xff0c;为开发者和运维人员带来了极大的便捷 。下面我们一起开始入门第四篇&#xff1a;查看容器资源、查看容器详细信息、…

基于数据挖掘的网络入侵检测关键技术研究

标题:基于数据挖掘的网络入侵检测关键技术研究 内容:1.摘要 随着互联网的迅速发展&#xff0c;网络安全问题日益严峻&#xff0c;网络入侵行为对个人、企业和国家的信息安全构成了巨大威胁。本文的目的是研究基于数据挖掘的网络入侵检测关键技术&#xff0c;以提高网络入侵检测…

中学数学几百年重大错误:将无穷多各异假R误为R——两数集相等的必要条件

中学数学几百年重大错误&#xff1a;将无穷多各异假R误为R——两数集相等的必要条件 黄小宁 设集A&#xff5b;x&#xff5d;表A各元均由x代表&#xff0c;相应变量x的变域是A。其余类推。本人多年前公开发表的论文中有定理&#xff1a; h定理&#xff08;两数集相等的必要条…

react-activation 实现页面保活记录

这里写目录标题 一、安装插件&#xff08;可选&#xff09;1、react-activation &#xff08;推荐&#xff09;2、umi-plugin-keep-alive 二、AliveScope的两种配置方式1、在src/app.ts 中配置2、在src/layout/index.tsx中配置 三、umi中的配置四、使用问题记录1、drop使用不生…

STM32使用红外避障传感器

1.1 介绍&#xff1a; 该传感器模块对环境光适应能力强&#xff0c;其具有一对红外线发射与接收管&#xff0c;发射管发射出一定频率的红外线&#xff0c;当检测方向遇到障碍物&#xff08;反射面&#xff09;时&#xff0c;红外线反射回来被接收管接收&#xff0c;经过比较器…

python tkinter 开发蓍草占卜系统

1. 项目概述 1.1 简介 蓍草占卜是中国传统的占卜方法&#xff0c;用于演算六十四卦。本系统通过现代编程技术&#xff0c;将传统的蓍草占卜方法数字化&#xff0c;提供一个准确、便捷的占卜工具。 蓍草占卜&#xff0c;作为中国古代的一种传统占卜方法&#xff0c;承载着深厚…

Linux搭建本地时间服务器及时间同步

搭建一个本地时间服务器&#xff0c;使得局域网内主机时间保持一致。 设置正确时间 # 设置系统时间 date -s "2025-03-25 17:31:00" # 将系统时间写入硬件时钟 hwclock --systohc时间服务器设置 系统应该预先安装chronyd 要允许 所有客户端 通过你的 chronyd 服务器…

2025-3-25算法打卡

一&#xff0c;走迷宫 1.题目描述&#xff1a; 给定一个 NMNM 的网格迷宫 GG。GG 的每个格子要么是道路&#xff0c;要么是障碍物&#xff08;道路用 11 表示&#xff0c;障碍物用 00 表示&#xff09;。 已知迷宫的入口位置为 (x1,y1)(x1​,y1​)&#xff0c;出口位置为 (x…

力扣刷题39. 组合总和

39. 组合总和 - 力扣&#xff08;LeetCode&#xff09; 需要定义一个index变量用来记录访问数组的下标&#xff0c;每次递归进行传参&#xff0c;在搜索过程中&#xff0c;因为为了避免重复数据&#xff0c;而且允许一个元素的重复出现&#xff0c;传入index时传入当前遍历的i…

ISIS-3 LSDB链路状态数据库同步

上一章我们介绍了ISIS的邻居建立关系以及ISIS的路由器角色有哪些,在不同的网络类型当中建立邻居关系有什么不同,并且以实验案例抓包的形式给大家进一步介绍了建立的过程。 这一章我们来介绍ISIS中是如何实现链路状态数据库同步的,与OSPF的链路状态同步有什么不同,在不同网络类…

Opencv计算机视觉编程攻略-第三节 图像颜色处理

第三节 图像颜色处理 1.颜色比较2.GrabCut分割图像3.色调、饱和度以及亮度 1.颜色比较 主要实现逐像素的颜色比较&#xff0c;其中注意BGR颜色空间不连续&#xff0c;不利于颜色提取和区分&#xff0c;转换到Lab空间&#xff1a; int getColorDistance(const cv::Vec3b& c…

BoomCut AI 技术创建本地化的营销视频

目录 视频翻译实验 交换实验 数字人实验 核心功能与技术亮点 适用场景 BoomCut 提供用于视频翻译、数字人等的 AI 技术,以快速创建本地化的营销视频 视频翻译实验 电影电影哪吒之魔童降世换成西班牙语

论华为 Pura X 折叠屏性能检测

在科技浪潮中&#xff0c;折叠屏手机以其创新形态掀起市场热潮。华为 Pura X 作为华为最新折叠手机&#xff0c;承载前沿科技与精湛工艺&#xff0c;成为行业焦点。它融合先进折叠屏技术与优质材质&#xff0c;致力于打破传统手机使用边界&#xff0c;为用户开启全新体验。但产…

【蓝桥杯每日一题】3.25

&#x1f3dd;️专栏&#xff1a; 【蓝桥杯备篇】 &#x1f305;主页&#xff1a; f狐o狸x “OJ超时不是终点&#xff0c;是算法在提醒你该优化时间复杂度了&#xff01;” 目录 3.25 差分数组 一、一维差分 题目链接&#xff1a; 题目描述&#xff1a; 解题思路&#xff1a;…

3.25学习总结 抽象类和抽象方法+接口+内部类+API

抽象类和抽象方法&#xff1a; 有抽象方法&#xff0c;那么类肯定是抽象类。父类不一定是抽象的&#xff0c;但如果父类中有抽象方法那一定是抽象类。 如果子类中都存在吃这个行为&#xff0c;但吃的具体东西不同&#xff0c;那么吃这个行为定义在父类里面就是抽象方法&#x…