图论入门:Dijkstra最短路径算法详解
前言在现实生活中我们经常会遇到这样的问题从A地到B地哪条路最近在计算机网络中数据包从源节点到目标节点走哪条路径最快这些问题本质上都是最短路径问题。而Dijkstra算法就是解决这类问题最经典、最常用的算法之一。一、什么是Dijkstra算法Dijkstra算法又称迪杰斯特拉算法是由荷兰计算机科学家Edsger W. Dijkstra在1956年提出、于1959年公开发表的一种经典算法。它用于解决单源最短路径问题——即从一个起点出发找到该起点到图中所有其他顶点的最短路径。简单来说给定一个起点Dijkstra算法能告诉你到图中每一个其他点最短要走多远。单源的意思是只有一个起点。如果你想求任意两点之间的最短路径可以多次运行Dijkstra算法每个点作为起点跑一次算法的适用条件Dijkstra算法有一个重要的前提图中所有边的权重必须是非负数。如果存在负权边Dijkstra算法可能会得到错误的结果。对于含负权边的情况可以使用Bellman-Ford算法。二、核心思想贪心策略Dijkstra算法的核心思想是贪心——每一步都选择当前看来最优的节点。具体来说算法将图中的顶点分为两组已确定最短路径的顶点集合S这些顶点的最短距离已经最终确定不会再改变未确定最短路径的顶点集合U这些顶点的最短距离还在更新中算法每次从集合U中选出距离起点最近的那个顶点将其移入集合S然后尝试通过这个顶点去松弛更新其他顶点的距离。松弛操作Relaxation如果通过当前节点u能到达邻居v且起点到u的距离 u到v的边的权重 起点到v的当前距离那么就更新v的距离。简单说就是走u这条路会不会更近三、算法步骤Dijkstra算法的执行流程可以分为以下几个步骤第一步初始化创建一个距离数组dist[]记录起点到每个顶点的当前最短距离将起点的距离设为0其他所有顶点的距离设为无穷大∞创建一个集合或数组记录哪些顶点已经被处理已确定最短路径第二步选择当前距离最小的未处理顶点从所有未处理的顶点中选出dist[]值最小的那个顶点 u。第三步松弛更新遍历顶点 u 的所有邻居 v如果dist[u] weight(u, v) dist[v]则更新dist[v] dist[u] weight(u, v)这表示通过 u 走到 v 比之前记录的路径更短第四步标记已处理将顶点 u 标记为已处理已确定最短路径之后不再更新它。第五步重复重复步骤二到步骤四直到所有顶点都被处理完毕。四、图解示例4.1 示例图本次使用的图包含 6 个顶点A, B, C, D, E, F所有边均为无向边即可以双向通行边上的数字代表权重距离/成本。对应的边列表起点终点权重AB3AC1AE1BC2BD4BE3CD1CF1DE6DF3EF64.2 算法执行过程我们的起点为A。初始状态顶点ABCDEF距离0∞∞∞∞∞已处理✅❌❌❌❌❌第 1 轮处理 A从 A 出发查看 A 的所有邻居到B距离 0 3 3到C距离 0 1 1到E距离 0 1 1更新距离表顶点ABCDEF距离031∞1∞已处理✅❌❌❌❌❌此时未处理顶点中距离最小的是C距离 1和E距离 1。我们以此来处理。第 2 轮处理 C当前节点 C 的距离为 1。查看 C 的所有邻居A, B, D, F到B1 2 3不大于现有的 3不更新到D1 1 2更新 D 2到F1 1 2更新 F 2顶点ABCDEF距离031212已处理✅❌✅❌❌❌第 3 轮处理 E当前节点 E 的距离为 1。查看 E 的所有邻居A, B, D, F到B1 3 4大于现有的 3不更新到D1 6 7大于现有的 2不更新到F1 6 7大于现有的 2不更新顶点ABCDEF距离031212已处理✅❌✅❌✅❌第 4 轮处理 D当前节点 D 的距离为 2。查看 D 的所有邻居B, C, E, F到B2 4 6大于现有的 3不更新到F2 3 5大于现有的 2不更新顶点ABCDEF距离031212已处理✅❌✅✅✅❌第 5 轮处理 F当前节点 F 的距离为 2。查看 F 的所有邻居C, D, E到 C213大于1不更新到 D235大于2不更新到 E268大于1不更新顶点ABCDEF距离031212已处理✅❌✅✅✅✅第 6 轮处理 B当前节点 B 的距离为 3。查看 B 的所有邻居A, C, D, E均不满足更新条件算法结束。4.3 最终结果从 A 到各顶点的最短距离为顶点ABCDEF最短距离031212验证A→D 的最短路径是A → C → D权重 112而不是走 A→E→D167。✅五、Java 代码实现在实际应用中我们通常使用优先队列最小堆来优化每次找距离最小的未处理顶点这一操作将时间复杂度从 O(V²) 降到 O((VE)logV)。5.1 完整代码public class Dijkstra { /** * Dijkstra 算法优先队列优化版 * param n 顶点个数 * param graph 邻接表graph.get(u) 返回 int[]{v, w} 数组v是邻居w是权重 * param start 起点编号 * return 从起点到各顶点的最短距离数组 */ public static int[] dijkstra(int n, ListListint[] graph, int start) { // 1. 距离数组初始化为无穷大 int[] dist new int[n]; Arrays.fill(dist, Integer.MAX_VALUE); dist[start] 0; // 2. 优先队列最小堆存储 [节点编号, 当前距离] PriorityQueueint[] pq new PriorityQueue(Comparator.comparingInt(a - a[1])); //队列里的所有包裹节点谁离起点的距离a[1]小谁就排在队头优先被弹出 pq.offer(new int[]{start, 0}); // 3. 标记数组记录顶点是否已确定最短路径 boolean[] visited new boolean[n]; while (!pq.isEmpty()) { // 弹出距离最小的节点 int[] cur pq.poll(); int u cur[0];//u是当前节点编号 // 如果已经处理过跳过避免重复处理过时数据 if (visited[u]) continue; visited[u] true; // 4. 遍历所有邻居进行松弛操作 for (int[] edge : graph.get(u)) { int v edge[0]; // 邻居顶点 int w edge[1]; // 边权重 // 如果通过 u 到 v 更短则更新 if (dist[u] w dist[v]) { dist[v] dist[u] w; pq.offer(new int[]{v, dist[v]}); } } } return dist; } // 辅助方法添加无向边双向添加 private static void addEdge(ListListint[] graph, int u, int v, int w) { graph.get(u).add(new int[]{v, w}); graph.get(v).add(new int[]{u, w}); } public static void main(String[] args) { // 顶点编号A0, B1, C2, D3, E4, F5 int n 6; // 初始化邻接表 ListListint[] graph new ArrayList(); for (int i 0; i n; i) { graph.add(new ArrayList()); } // 添加边对应示例图 addEdge(graph, 0, 1, 3); // A-B addEdge(graph, 0, 2, 1); // A-C addEdge(graph, 0, 4, 1); // A-E addEdge(graph, 1, 2, 2); // B-C addEdge(graph, 1, 3, 4); // B-D addEdge(graph, 1, 4, 3); // B-E addEdge(graph, 2, 3, 1); // C-D addEdge(graph, 2, 5, 1); // C-F addEdge(graph, 3, 4, 6); // D-E addEdge(graph, 3, 5, 3); // D-F addEdge(graph, 4, 5, 6); // E-F // 运行算法起点为 A(0) int[] result dijkstra(n, graph, 0); // 打印结果 char[] names {A, B, C, D, E, F}; for (int i 0; i n; i) { System.out.println(到 names[i] 的最短距离为: result[i]); } // 输出 // 到 A 的最短距离为: 0 // 到 B 的最短距离为: 3 // 到 C 的最短距离为: 1 // 到 D 的最短距离为: 2 // 到 E 的最短距离为: 1 // 到 F 的最短距离为: 2 } }5.2 代码逐行解读① 为什么用Integer.MAX_VALUE表示无穷大因为起点到其他点一开始未知看作无穷远方便后面被更小的值替换。② 优先队列里放int[]它怎么排序的PriorityQueueint[] pq new PriorityQueue(Comparator.comparingInt(a - a[1]));这行代码的意思是队列里放的是{节点, 距离}按照距离a[1]从小到大每次pq.poll()拿到的都是距离最短的那个节点。③visited数组是干什么的当某个节点的最短距离确定后后面可能还会因为别的路径产生一个较大的距离被丢进队列过时数据。visited确保每个节点只处理一次一旦确定最短距离就不再理它。④ 核心三行松弛操作javaif (dist[u] w dist[v]) { dist[v] dist[u] w; pq.offer(new int[]{v, dist[v]}); }当前站在 u走到 u 已经花了dist[u]的距离如果从 u 再到 v总花费dist[u] w比之前记录的dist[v]更小说明找到近路了赶紧更新dist[v]并把 v 丢进队列等着用 v 去更新它的邻居六、时间复杂度分析Dijkstra算法有两种常见的实现方式实现方式时间复杂度适用场景朴素实现数组遍历O(V²)稠密图边多优先队列优化二叉堆O((VE)logV)稀疏图边少其中 V 是顶点数E 是边数。因为边少时优先队列省掉了朴素算法每次遍历全部顶点找最小值的巨额固定开销O(V²)而边多时优先队列需要为每条边支付一次昂贵的堆调整费用O(logV)累计成本E·logV超过了朴素算法仅做简单数组扫描和赋值的代价所以边少用堆省扫描费边多用朴素省堆调整费。对于大多数实际应用场景如地图导航、网络路由图通常是稀疏的因此优先队列优化版本更为常用。七、实际应用场景Dijkstra算法的应用非常广泛几乎涉及到所有需要找最短/最快路径的场景地图导航GPS设备使用Dijkstra算法计算从当前位置到目的地的最短或最快路线网络路由OSPF开放最短路径优先协议就是基于Dijkstra算法来计算数据包的最优传输路径通信网络设计规划基站、光纤等网络设施的最优布局机器人路径规划让机器人在有障碍物的环境中找到从起点到终点的最优路径游戏AI计算游戏中角色移动到目标位置的最短路径物流运输优化规划配送路线节省时间和成本八、算法局限性在使用Dijkstra算法之前有几个重要的限制需要了解不能处理负权边如果图中存在权重为负的边Dijkstra算法可能给出错误的结果。对于这种情况应该使用Bellman-Ford算法。只能求单源最短路径Dijkstra算法只能从一个起点出发求到其他所有点的最短路径。不能处理大规模动态图如果图的结构频繁变化如实时路况每次重新运行Dijkstra算法的开销可能很大需要考虑其他动态算法。九、总结让我们用一张流程图来回顾Dijkstra算法的核心流程text初始化 (起点0, 其他∞) ↓ 从未处理节点中 选出距离最小的u ↓ 通过u松弛更新邻居 ↓ 标记u为已处理 ↓ 所有节点已处理 ↙ ↘ 否 是 ↖ ↓ ───── 结束记住三个关键词单源从一个起点出发贪心每次都选当前最近的节点松弛通过已选节点去更新其他节点的距离Dijkstra算法是图论中最基础也最重要的算法之一。掌握了它你就拿到了解决最短路径问题的钥匙。无论是在算法竞赛、面试还是实际项目中它都是一个高频出现的工具。

相关新闻

KiCad中GND符号的分类与应用指南

KiCad中GND符号的分类与应用指南

1. KiCad中的GND符号概述在电子设计自动化(EDA)软件KiCad中,GND(地)符号是原理图设计中最重要的元素之一。与普通信号连接不同,GND符号代表着电路中的参考电位点,是电流回流的公共路径。KiCad提供了多种GND符号类型&am…

2026/7/17 3:59:56 阅读更多 →
具身智能的硬件平台:物理化身即第一性原理

具身智能的硬件平台:物理化身即第一性原理

1. 为什么“具身智能”不能只活在代码里:物理化身不是附加项,而是第一性原理“具身智能”这个词最近在技术圈被反复提起,但很多人一听到就下意识点开论文PDF,或者直接跳转到大模型API调用文档——这恰恰踩进了最根本的认知陷阱。我…

2026/7/17 3:59:56 阅读更多 →
RK3568电源系统设计与DCDC电路布局要点解析

RK3568电源系统设计与DCDC电路布局要点解析

1. RK3568电源系统架构解析作为Rockchip新一代中高端SoC,RK3568的电源设计直接关系到系统稳定性和性能释放。这颗22nm工艺的四核A55处理器集成了丰富的外设接口,其电源需求呈现出"多电压域、大电流动态切换"的特点。从热词分析可见&#xff0c…

2026/7/17 3:59:56 阅读更多 →

最新新闻

Python安全编程实战:输入验证与数据保护完整指南

Python安全编程实战:输入验证与数据保护完整指南

1. 项目概述:为什么Python安全编程是每个开发者的必修课?最近在整理一个名为gh_mirrors/ma/materials的仓库时,我发现里面关于安全编程的材料特别零散,尤其是输入验证和数据保护这块,很多示例要么过时,要么…

2026/7/17 5:04:24 阅读更多 →
【C语言学习004】——变量

【C语言学习004】——变量

上节的最后我们初步有了写“变量”的想法,我们再来细致的讨论一下:我们想要在程序运行后手动输入一个数字,因此我们的程序得能够储存我们所输入的数字。此外,我们需要让我们输入的数字能够真正参与后续的运算,因此我们…

2026/7/17 5:02:23 阅读更多 →
C++异常处理全解析:从语法到工程实践,构建健壮程序

C++异常处理全解析:从语法到工程实践,构建健壮程序

1. 项目概述:为什么C需要异常处理?干了这么多年C开发,我见过太多因为错误处理不当而导致的“惨案”。程序在测试环境跑得好好的,一到线上就莫名其妙崩溃,日志里留下一句“Segmentation fault”就再无下文;或…

2026/7/17 5:00:22 阅读更多 →
C++异步日志与网络日志实现:生产者-消费者模型与双缓冲区技术详解

C++异步日志与网络日志实现:生产者-消费者模型与双缓冲区技术详解

1. 项目概述与核心价值如果你正在用C做项目,无论是桌面应用、服务器后台还是嵌入式系统,日志功能绝对是绕不开的一环。它就像是项目的“黑匣子”,程序在线上跑得怎么样,出了什么问题,性能瓶颈在哪里,全靠它…

2026/7/17 4:58:20 阅读更多 →
C++工程调试与错误处理:异常、断言与日志系统构建实战

C++工程调试与错误处理:异常、断言与日志系统构建实战

1. 项目概述:为什么调试与错误处理是C工程的基石干了十多年C,从桌面应用到后台服务,再到嵌入式系统,我踩过最多的坑,往往不是算法有多复杂,而是程序在某个意想不到的时刻突然崩溃,或者在生产环境…

2026/7/17 4:58:20 阅读更多 →
OpenClaw全平台部署生存指南:环境、模型、渠道与技能四层调优

OpenClaw全平台部署生存指南:环境、模型、渠道与技能四层调优

1. 这不是安装教程,是2026年OpenClaw全平台部署的“生存指南” 我第一次在Mac上敲下 curl -fsSL https://openclaw.ai/install.sh | bash 时,以为只是装个AI助手。结果三小时后,我的终端里堆满了 ECONNRESET 、 device identity requir…

2026/7/17 4:58:20 阅读更多 →

日新闻

2026全国外贸独立站搭建公司推荐排行,含零代码SAAS、AI编程、源码定制

2026全国外贸独立站搭建公司推荐排行,含零代码SAAS、AI编程、源码定制

2026全国外贸独立站搭建公司推荐排行 外贸工厂、工贸企业建设官网,核心需求与普通展示型网站并不相同。除了页面整洁、多语言适配和海外访问体验,企业还要重点考虑数据能否导出、站点能否迁移、源码是否可控、后续费用是否透明,以及更换服务…

2026/7/17 0:02:10 阅读更多 →
HarmonyOS 应用开发《掌上英语》第19篇:3D 翻转动画实现——ArkTS 动画系统全解析

HarmonyOS 应用开发《掌上英语》第19篇:3D 翻转动画实现——ArkTS 动画系统全解析

3D 翻转动画实现——ArkTS 动画系统全解析引言 在移动应用中,卡片翻转动画是最受欢迎的交互动效之一,它能给用户带来直观的"物理世界"操作感。在我们的英语学习 App 的单词学习页面(CourseHomePage.ets)中,就…

2026/7/17 0:02:10 阅读更多 →
2026键盘推荐|IQUNIX EV63多场景适配,不允许有人不知道!

2026键盘推荐|IQUNIX EV63多场景适配,不允许有人不知道!

现在很多用户都需要一款能适配多场景的键盘,既能满足交流码字的需求,又能应对居家电竞的性能需求,最好还得有点小颜值。而大多键盘都只能兼顾其中一两个场景。这次我实测了IQUNIX EV63三款配色(银武士、黑武士、紫罗兰&#xff09…

2026/7/17 0:04:10 阅读更多 →

周新闻

互联网大厂 Java 求职面试:燕双非的搞笑回答与技术探讨

互联网大厂 Java 求职面试:燕双非的搞笑回答与技术探讨

互联网大厂 Java 求职面试:燕双非的搞笑回答与技术探讨 在一个阳光明媚的上午,互联网大厂的面试官坐在桌前,准备迎接他的面试候选人——燕双非,一个以搞笑和幽默著称的程序员。第一轮提问 面试官:燕双非,作…

2026/7/17 3:22:28 阅读更多 →
车载以太网PMA测试设备选型:示波器、VNA、信号源3类仪器关键参数与预算评估

车载以太网PMA测试设备选型:示波器、VNA、信号源3类仪器关键参数与预算评估

车载以太网PMA测试设备选型:示波器、VNA、信号源3类仪器关键参数与预算评估在智能驾驶和车联网技术快速发展的今天,车载以太网作为新一代车载网络的核心传输技术,其物理层性能直接决定了数据传输的可靠性和稳定性。1000BASE-T1作为当前主流的…

2026/7/17 3:01:28 阅读更多 →
VSCode EIDE 插件 2.0:APM32/STM32 项目迁移实战,5步完成Keil工程转换

VSCode EIDE 插件 2.0:APM32/STM32 项目迁移实战,5步完成Keil工程转换

VSCode EIDE 插件 2.0:APM32/STM32 项目迁移实战指南嵌入式开发领域正经历一场工具链的静默革命。当传统Keil用户首次打开VSCode的扩展市场搜索EIDE时,往往会惊讶于这个看似简单的插件竟能重构十余年的开发习惯。本文将揭示如何用五个精准步骤&#xff0…

2026/7/16 23:40:37 阅读更多 →

月新闻