参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们受益!

# dijkstra(朴素版)精讲 [卡码网:47. 参加科学大会](https://kamacoder.com/problempage.php?pid=1047) 【题目描述】 小明是一位科学家,他需要参加一场重要的国际科学大会,以展示自己的最新研究成果。 小明的起点是第一个车站,终点是最后一个车站。然而,途中的各个车站之间的道路状况、交通拥堵程度以及可能的自然因素(如天气变化)等不同,这些因素都会影响每条路径的通行时间。 小明希望能选择一条花费时间最少的路线,以确保他能够尽快到达目的地。 【输入描述】 第一行包含两个正整数,第一个正整数 N 表示一共有 N 个公共汽车站,第二个正整数 M 表示有 M 条公路。 接下来为 M 行,每行包括三个整数,S、E 和 V,代表了从 S 车站可以单向直达 E 车站,并且需要花费 V 单位的时间。 【输出描述】 输出一个整数,代表小明从起点到终点所花费的最小时间。 输入示例 ``` 7 9 1 2 1 1 3 4 2 3 2 2 4 5 3 4 2 4 5 3 2 6 4 5 7 4 6 7 9 ``` 输出示例:12 【提示信息】 能够到达的情况: 如下图所示,起始车站为 1 号车站,终点车站为 7 号车站,绿色路线为最短的路线,路线总长度为 12,则输出 12。 ![](https://code-thinking-1253855093.file.myqcloud.com/pics/20240227101345.png) 不能到达的情况: 如下图所示,当从起始车站不能到达终点车站时,则输出 -1。 ![](https://code-thinking-1253855093.file.myqcloud.com/pics/20240227101401.png) 数据范围: 1 <= N <= 500; 1 <= M <= 5000; ## 思路 本题就是求最短路,最短路是图论中的经典问题即:给出一个有向图,一个起点,一个终点,问起点到终点的最短路径。 接下来,我们来详细讲解最短路算法中的 dijkstra 算法。 dijkstra算法:在有权图(权值非负数)中求从起点到其他节点的最短路径算法。 需要注意两点: * dijkstra 算法可以同时求 起点到所有节点的最短路径 * 权值不能为负数 (这两点后面我们会讲到) 如本题示例中的图: ![](https://code-thinking-1253855093.file.myqcloud.com/pics/20240125162647.png) 起点(节点1)到终点(节点7) 的最短路径是 图中 标记绿线的部分。 最短路径的权值为12。 其实 dijkstra 算法 和 我们之前讲解的prim算法思路非常接近,如果大家认真学过[prim算法](./0053.寻宝-prim.md),那么理解 Dijkstra 算法会相对容易很多。(这也是我要先讲prim再讲dijkstra的原因) dijkstra 算法 同样是贪心的思路,不断寻找距离 源点最近的没有访问过的节点。 这里我也给出 **dijkstra三部曲**: 1. 第一步,选源点到哪个节点近且该节点未被访问过 2. 第二步,该最近节点被标记访问过 3. 第三步,更新非访问节点到源点的距离(即更新minDist数组) 大家此时已经会发现,这和prim算法 怎么这么像呢。 我在[prim算法](./0053.寻宝-prim.md)讲解中也给出了三部曲。 prim 和 dijkstra 确实很像,思路也是类似的,这一点我在后面还会详细来讲。 在dijkstra算法中,同样有一个数组很重要,起名为:minDist。 **minDist数组 用来记录 每一个节点距离源点的最小距离**。 理解这一点很重要,也是理解 dijkstra 算法的核心所在。 大家现在看着可能有点懵,不知道什么意思。 没关系,先让大家有一个印象,对理解后面讲解有帮助。 我们先来画图看一下 dijkstra 的工作过程,以本题示例为例: (以下为朴素版dijkstra的思路) (**示例中节点编号是从1开始,所以为了让大家看的不晕,minDist数组下标我也从 1 开始计数,下标0 就不使用了,这样 下标和节点标号就可以对应上了,避免大家搞混**) ## 朴素版dijkstra ### 模拟过程 ----------- 0、初始化 minDist数组数值初始化为int最大值。 这里在强点一下 **minDist数组的含义:记录所有节点到源点的最短路径**,那么初始化的时候就应该初始为最大值,这样才能在后续出现最短路径的时候及时更新。 ![](https://code-thinking-1253855093.file.myqcloud.com/pics/20240130115306.png) (图中,max 表示默认值,节点0 不做处理,统一从下标1 开始计算,这样下标和节点数值统一, 方便大家理解,避免搞混) 源点(节点1) 到自己的距离为0,所以 minDist[1] = 0 此时所有节点都没有被访问过,所以 visited数组都为0 --------------- 以下为dijkstra 三部曲 1、选源点到哪个节点近且该节点未被访问过 源点距离源点最近,距离为0,且未被访问。 2、该最近节点被标记访问过 标记源点访问过 3、更新非访问节点到源点的距离(即更新minDist数组) ,如图: ![](https://code-thinking-1253855093.file.myqcloud.com/pics/20240130115421.png) 更新 minDist数组,即:源点(节点1) 到 节点2 和 节点3的距离。 * 源点到节点2的最短距离为1,小于原minDist[2]的数值max,更新minDist[2] = 1 * 源点到节点3的最短距离为4,小于原minDist[3]的数值max,更新minDist[3] = 4 可能有录友问:为啥和 minDist[2] 比较? 再强调一下 minDist[2] 的含义,它表示源点到节点2的最短距离,那么目前我们得到了 源点到节点2的最短距离为1,小于默认值max,所以更新。 minDist[3]的更新同理 ------------- 1、选源点到哪个节点近且该节点未被访问过 未访问过的节点中,源点到节点2距离最近,选节点2 2、该最近节点被标记访问过 节点2被标记访问过 3、更新非访问节点到源点的距离(即更新minDist数组) ,如图: ![](https://code-thinking-1253855093.file.myqcloud.com/pics/20240130121240.png) 更新 minDist数组,即:源点(节点1) 到 节点6 、 节点3 和 节点4的距离。 **为什么更新这些节点呢? 怎么不更新其他节点呢**? 因为 源点(节点1)通过 已经计算过的节点(节点2) 可以链接到的节点 有 节点3,节点4和节点6. 更新 minDist数组: * 源点到节点6的最短距离为5,小于原minDist[6]的数值max,更新minDist[6] = 5 * 源点到节点3的最短距离为3,小于原minDist[3]的数值4,更新minDist[3] = 3 * 源点到节点4的最短距离为6,小于原minDist[4]的数值max,更新minDist[4] = 6 ------------------- 1、选源点到哪个节点近且该节点未被访问过 未访问过的节点中,源点距离哪些节点最近,怎么算的? 其实就是看 minDist数组里的数值,minDist 记录了 源点到所有节点的最近距离,结合visited数组筛选出未访问的节点就好。 从 上面的图,或者 从minDist数组中,我们都能看出 未访问过的节点中,源点(节点1)到节点3距离最近。 2、该最近节点被标记访问过 节点3被标记访问过 3、更新非访问节点到源点的距离(即更新minDist数组) ,如图: ![](https://code-thinking-1253855093.file.myqcloud.com/pics/20240130120434.png) 由于节点3的加入,那么源点可以有新的路径链接到节点4 所以更新minDist数组: 更新 minDist数组: * 源点到节点4的最短距离为5,小于原minDist[4]的数值6,更新minDist[4] = 5 ------------------ 1、选源点到哪个节点近且该节点未被访问过 距离源点最近且没有被访问过的节点,有节点4 和 节点6,距离源点距离都是 5 (minDist[4] = 5,minDist[6] = 5) ,选哪个节点都可以。 2、该最近节点被标记访问过 节点4被标记访问过 3、更新非访问节点到源点的距离(即更新minDist数组) ,如图: ![](https://code-thinking-1253855093.file.myqcloud.com/pics/20240201105335.png) 由于节点4的加入,那么源点可以链接到节点5 所以更新minDist数组: * 源点到节点5的最短距离为8,小于原minDist[5]的数值max,更新minDist[5] = 8 -------------- 1、选源点到哪个节点近且该节点未被访问过 距离源点最近且没有被访问过的节点,是节点6,距离源点距离是 5 (minDist[6] = 5) 2、该最近节点被标记访问过 节点6 被标记访问过 3、更新非访问节点到源点的距离(即更新minDist数组) ,如图: ![](https://code-thinking-1253855093.file.myqcloud.com/pics/20240201110250.png) 由于节点6的加入,那么源点可以链接到节点7 所以 更新minDist数组: * 源点到节点7的最短距离为14,小于原minDist[7]的数值max,更新minDist[7] = 14 ------------------- 1、选源点到哪个节点近且该节点未被访问过 距离源点最近且没有被访问过的节点,是节点5,距离源点距离是 8 (minDist[5] = 8) 2、该最近节点被标记访问过 节点5 被标记访问过 3、更新非访问节点到源点的距离(即更新minDist数组) ,如图: ![](https://code-thinking-1253855093.file.myqcloud.com/pics/20240201110651.png) 由于节点5的加入,那么源点有新的路径可以链接到节点7 所以 更新minDist数组: * 源点到节点7的最短距离为12,小于原minDist[7]的数值14,更新minDist[7] = 12 ----------------- 1、选源点到哪个节点近且该节点未被访问过 距离源点最近且没有被访问过的节点,是节点7(终点),距离源点距离是 12 (minDist[7] = 12) 2、该最近节点被标记访问过 节点7 被标记访问过 3、更新非访问节点到源点的距离(即更新minDist数组) ,如图: ![](https://code-thinking-1253855093.file.myqcloud.com/pics/20240201110920.png) 节点7加入,但节点7到节点7的距离为0,所以 不用更新minDist数组 -------------------- 最后我们要求起点(节点1) 到终点 (节点7)的距离。 再来回顾一下minDist数组的含义:记录 每一个节点距离源点的最小距离。 那么起到(节点1)到终点(节点7)的最短距离就是 minDist[7] ,按上面举例讲解来说,minDist[7] = 12,节点1 到节点7的最短路径为 12。 路径如图: ![](https://code-thinking-1253855093.file.myqcloud.com/pics/20240201111352.png) 在上面的讲解中,每一步 我都是按照 dijkstra 三部曲来讲解的,理解了这三部曲,代码也就好懂的。 ### 代码实现 本题代码如下,里面的 三部曲 我都做了注释,大家按照我上面的讲解 来看如下代码: ```CPP #include #include #include using namespace std; int main() { int n, m, p1, p2, val; cin >> n >> m; vector> grid(n + 1, vector(n + 1, INT_MAX)); for(int i = 0; i < m; i++){ cin >> p1 >> p2 >> val; grid[p1][p2] = val; } int start = 1; int end = n; // 存储从源点到每个节点的最短距离 std::vector minDist(n + 1, INT_MAX); // 记录顶点是否被访问过 std::vector visited(n + 1, false); minDist[start] = 0; // 起始点到自身的距离为0 for (int i = 1; i <= n; i++) { // 遍历所有节点 int minVal = INT_MAX; 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] != INT_MAX && minDist[cur] + grid[cur][v] < minDist[v]) { minDist[v] = minDist[cur] + grid[cur][v]; } } } if (minDist[end] == INT_MAX) cout << -1 << endl; // 不能到达终点 else cout << minDist[end] << endl; // 到达终点最短路径 } ``` * 时间复杂度:O(n^2) * 空间复杂度:O(n^2) ### debug方法 写这种题目难免会有各种各样的问题,我们如何发现自己的代码是否有问题呢? 最好的方式就是打日志,本题的话,就是将 minDist 数组打印出来,就可以很明显发现 哪里出问题了。 每次选择节点后,minDist数组的变化是否符合预期 ,是否和我上面讲的逻辑是对应的。 例如本题,如果想debug的话,打印日志可以这样写: ```CPP #include #include #include using namespace std; int main() { int n, m, p1, p2, val; cin >> n >> m; vector> grid(n + 1, vector(n + 1, INT_MAX)); for(int i = 0; i < m; i++){ cin >> p1 >> p2 >> val; grid[p1][p2] = val; } int start = 1; int end = n; std::vector minDist(n + 1, INT_MAX); std::vector visited(n + 1, false); minDist[start] = 0; for (int i = 1; i <= n; i++) { int minVal = INT_MAX; int cur = 1; for (int v = 1; v <= n; ++v) { if (!visited[v] && minDist[v] < minVal) { minVal = minDist[v]; cur = v; } } visited[cur] = true; for (int v = 1; v <= n; v++) { if (!visited[v] && grid[cur][v] != INT_MAX && minDist[cur] + grid[cur][v] < minDist[v]) { minDist[v] = minDist[cur] + grid[cur][v]; } } // 打印日志: cout << "select:" << cur << endl; for (int v = 1; v <= n; v++) cout << v << ":" << minDist[v] << " "; cout << endl << endl;; } if (minDist[end] == INT_MAX) cout << -1 << endl; else cout << minDist[end] << endl; } ``` 打印后的结果: ``` select:1 1:0 2:1 3:4 4:2147483647 5:2147483647 6:2147483647 7:2147483647 select:2 1:0 2:1 3:3 4:6 5:2147483647 6:5 7:2147483647 select:3 1:0 2:1 3:3 4:5 5:2147483647 6:5 7:2147483647 select:4 1:0 2:1 3:3 4:5 5:8 6:5 7:2147483647 select:6 1:0 2:1 3:3 4:5 5:8 6:5 7:14 select:5 1:0 2:1 3:3 4:5 5:8 6:5 7:12 select:7 1:0 2:1 3:3 4:5 5:8 6:5 7:12 ``` 打印日志可以和上面我讲解的过程进行对比,每一步的结果是完全对应的。 所以如果大家如果代码有问题,打日志来debug是最好的方法 ### 如何求路径 如果题目要求把最短路的路径打印出来,应该怎么办呢? 这里还是有一些“坑”的,本题打印路径和 prim 打印路径是一样的,我在 [prim算法精讲](./0053.寻宝-prim.md) 【拓展】中 已经详细讲解了。 在这里就不再赘述。 打印路径只需要添加 几行代码, 打印路径的代码我都加上的日志,如下: ```CPP #include #include #include using namespace std; int main() { int n, m, p1, p2, val; cin >> n >> m; vector> grid(n + 1, vector(n + 1, INT_MAX)); for(int i = 0; i < m; i++){ cin >> p1 >> p2 >> val; grid[p1][p2] = val; } int start = 1; int end = n; std::vector minDist(n + 1, INT_MAX); std::vector visited(n + 1, false); minDist[start] = 0; //加上初始化 vector parent(n + 1, -1); for (int i = 1; i <= n; i++) { int minVal = INT_MAX; int cur = 1; for (int v = 1; v <= n; ++v) { if (!visited[v] && minDist[v] < minVal) { minVal = minDist[v]; cur = v; } } visited[cur] = true; for (int v = 1; v <= n; v++) { if (!visited[v] && grid[cur][v] != INT_MAX && minDist[cur] + grid[cur][v] < minDist[v]) { minDist[v] = minDist[cur] + grid[cur][v]; parent[v] = cur; // 记录边 } } } // 输出最短情况 for (int i = 1; i <= n; i++) { cout << parent[i] << "->" << i << endl; } } ``` 打印结果: ``` -1->1 1->2 2->3 3->4 4->5 2->6 5->7 ``` 对应如图: ![](https://code-thinking-1253855093.file.myqcloud.com/pics/20240201111352.png) ### 出现负数 如果图中边的权值为负数,dijkstra 还合适吗? 看一下这个图: (有负权值) ![](https://code-thinking-1253855093.file.myqcloud.com/pics/20240227104334.png) 节点1 到 节点5 的最短路径 应该是 节点1 -> 节点2 -> 节点3 -> 节点4 -> 节点5 那我们来看dijkstra 求解的路径是什么样的,继续dijkstra 三部曲来模拟 :(dijkstra模拟过程上面已经详细讲过,以下只模拟重要过程,例如如何初始化就省略讲解了) ----------- 初始化: ![](https://code-thinking-1253855093.file.myqcloud.com/pics/20240227104801.png) --------------- 1、选源点到哪个节点近且该节点未被访问过 源点距离源点最近,距离为0,且未被访问。 2、该最近节点被标记访问过 标记源点访问过 3、更新非访问节点到源点的距离(即更新minDist数组) ,如图: ![](https://code-thinking-1253855093.file.myqcloud.com/pics/20240227110217.png) 更新 minDist数组,即:源点(节点1) 到 节点2 和 节点3的距离。 * 源点到节点2的最短距离为100,小于原minDist[2]的数值max,更新minDist[2] = 100 * 源点到节点3的最短距离为1,小于原minDist[3]的数值max,更新minDist[4] = 1 ------------------- 1、选源点到哪个节点近且该节点未被访问过 源点距离节点3最近,距离为1,且未被访问。 2、该最近节点被标记访问过 标记节点3访问过 3、更新非访问节点到源点的距离(即更新minDist数组) ,如图: ![](https://code-thinking-1253855093.file.myqcloud.com/pics/20240227110330.png) 由于节点3的加入,那么源点可以有新的路径链接到节点4 所以更新minDist数组: * 源点到节点4的最短距离为2,小于原minDist[4]的数值max,更新minDist[4] = 2 -------------- 1、选源点到哪个节点近且该节点未被访问过 源点距离节点4最近,距离为2,且未被访问。 2、该最近节点被标记访问过 标记节点4访问过 3、更新非访问节点到源点的距离(即更新minDist数组) ,如图: ![](https://code-thinking-1253855093.file.myqcloud.com/pics/20240227110346.png) 由于节点4的加入,那么源点可以有新的路径链接到节点5 所以更新minDist数组: * 源点到节点5的最短距离为3,小于原minDist[5]的数值max,更新minDist[5] = 5 ------------ 1、选源点到哪个节点近且该节点未被访问过 源点距离节点5最近,距离为3,且未被访问。 2、该最近节点被标记访问过 标记节点5访问过 3、更新非访问节点到源点的距离(即更新minDist数组) ,如图: ![](https://code-thinking-1253855093.file.myqcloud.com/pics/20240227110405.png) 节点5的加入,而节点5 没有链接其他节点, 所以不用更新minDist数组,仅标记节点5被访问过了 ------------ 1、选源点到哪个节点近且该节点未被访问过 源点距离节点2最近,距离为100,且未被访问。 2、该最近节点被标记访问过 标记节点2访问过 3、更新非访问节点到源点的距离(即更新minDist数组) ,如图: ![](https://code-thinking-1253855093.file.myqcloud.com/pics/20240227110711.png) -------------- 至此dijkstra的模拟过程就结束了,根据最后的minDist数组,我们求 节点1 到 节点5 的最短路径的权值总和为 3,路径: 节点1 -> 节点3 -> 节点4 -> 节点5 通过以上的过程模拟,我们可以发现 之所以 没有走有负权值的最短路径 是因为 在 访问 节点 2 的时候,节点 3 已经访问过了,就不会再更新了。 那有录友可能会想: 我可以改代码逻辑啊,访问过的节点,也让它继续访问不就好了? 那么访问过的节点还能继续访问会不会有死循环的出现呢?控制逻辑不让其死循环?那特殊情况自己能都想清楚吗?(可以试试,实践出真知) 对于负权值的出现,大家可以针对某一个场景 不断去修改 dijkstra 的代码,**但最终会发现只是 拆了东墙补西墙**,对dijkstra的补充逻辑只能满足某特定场景最短路求解。 对于求解带有负权值的最短路问题,可以使用 Bellman-Ford 算法 ,我在后序会详细讲解。 ## dijkstra与prim算法的区别 > 这里再次提示,需要先看我的 [prim算法精讲](./0053.寻宝-prim.md) ,否则可能不知道我下面讲的是什么。 大家可以发现 dijkstra的代码看上去 怎么和 prim算法这么像呢。 其实代码大体不差,唯一区别在 三部曲中的 第三步: 更新minDist数组 因为**prim是求 非访问节点到最小生成树的最小距离,而 dijkstra是求 非访问节点到源点的最小距离**。 prim 更新 minDist数组的写法: ```CPP for (int j = 1; j <= v; j++) { if (!isInTree[j] && grid[cur][j] < minDist[j]) { minDist[j] = grid[cur][j]; } } ``` 因为 minDist表示 节点到最小生成树的最小距离,所以 新节点cur的加入,只需要 使用 grid[cur][j] ,grid[cur][j] 就表示 cur 加入生成树后,生成树到 节点j 的距离。 dijkstra 更新 minDist数组的写法: ```CPP for (int v = 1; v <= n; v++) { if (!visited[v] && grid[cur][v] != INT_MAX && minDist[cur] + grid[cur][v] < minDist[v]) { minDist[v] = minDist[cur] + grid[cur][v]; } } ``` 因为 minDist表示 节点到源点的最小距离,所以 新节点 cur 的加入,需要使用 源点到cur的距离 (minDist[cur]) + cur 到 节点 v 的距离 (grid[cur][v]),才是 源点到节点v的距离。 此时大家可能不禁要想 prim算法 可以有负权值吗? 当然可以! 录友们可以自己思考思考一下,这是为什么? 这里我提示一下:prim算法只需要将节点以最小权值和链接在一起,不涉及到单一路径。 ## 总结 本篇,我们深入讲解的dijkstra算法,详细模拟其工作的流程。 这里我给出了 **dijkstra 三部曲 来 帮助大家理解 该算法**,不至于 每次写 dijkstra 都是黑盒操作,没有框架没有章法。 在给出的代码中,我也按照三部曲的逻辑来给大家注释,只要理解这三部曲,即使 过段时间 对 dijkstra 算法有些遗忘,依然可以写出一个框架出来,然后再去调试细节。 对于图论算法,一般代码都比较长,很难写出代码直接可以提交通过,都需要一个debug的过程,所以 **学习如何debug 非常重要**! 这也是我为什么 在本文中 单独用来讲解 debug方法。 本题求的是最短路径和是多少,**同时我们也要掌握 如何把最短路径打印出来**。 我还写了大篇幅来讲解 负权值的情况, 只有画图带大家一步一步去 看 出现负权值 dijkstra的求解过程,才能帮助大家理解,问题出在哪里。 如果我直接讲:是**因为访问过的节点 不能再访问,导致错过真正的最短路**,我相信大家都不知道我在说啥。 最后我还讲解了 dijkstra 和 prim 算法的 相同 与 不同之处, 我在图论的讲解安排中 先讲 prim算法 再讲 dijkstra 是有目的的, **理解这两个算法的相同与不同之处 有助于大家学习的更深入**。 而不是 学了 dijkstra 就只看 dijkstra, 算法之间 都是有联系的,多去思考 算法之间的相互联系,会帮助大家思考的更深入,掌握的更彻底。 本篇写了这么长,我也只讲解了 朴素版dijkstra,**关于 堆优化dijkstra,我会在下一篇再来给大家详细讲解**。 加油 ## 其他语言版本 ### Java ```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]); // 到达终点最短路径 } } } ``` ### Python ``` import sys def dijkstra(n, m, edges, start, end): # 初始化邻接矩阵 grid = [[float('inf')] * (n + 1) for _ in range(n + 1)] for p1, p2, val in edges: grid[p1][p2] = val # 初始化距离数组和访问数组 minDist = [float('inf')] * (n + 1) visited = [False] * (n + 1) minDist[start] = 0 # 起始点到自身的距离为0 for _ in range(1, n + 1): # 遍历所有节点 minVal = float('inf') cur = -1 # 选择距离源点最近且未访问过的节点 for v in range(1, n + 1): if not visited[v] and minDist[v] < minVal: minVal = minDist[v] cur = v if cur == -1: # 如果找不到未访问过的节点,提前结束 break visited[cur] = True # 标记该节点已被访问 # 更新未访问节点到源点的距离 for v in range(1, n + 1): if not visited[v] and grid[cur][v] != float('inf') and minDist[cur] + grid[cur][v] < minDist[v]: minDist[v] = minDist[cur] + grid[cur][v] return -1 if minDist[end] == float('inf') else minDist[end] if __name__ == "__main__": input = sys.stdin.read data = input().split() n, m = int(data[0]), int(data[1]) edges = [] index = 2 for _ in range(m): p1 = int(data[index]) p2 = int(data[index + 1]) val = int(data[index + 2]) edges.append((p1, p2, val)) index += 3 start = 1 # 起点 end = n # 终点 result = dijkstra(n, m, edges, start, end) print(result) ``` ### Go ### Rust ### Javascript ```js function dijkstra(grid, start, end) { const visited = Array.from({length: end + 1}, () => false) const minDist = Array.from({length: end + 1}, () => Number.MAX_VALUE) minDist[start] = 0 for (let i = 1 ; i < end + 1 ; i++) { let cur = -1 let tempMinDist = Number.MAX_VALUE // 1. 找尋與起始點距離最近且未被訪的節點 for (let j = 1 ; j < end + 1 ; j++) { if (!visited[j] && minDist[j] < tempMinDist) { cur = j tempMinDist = minDist[j] } } if (cur === -1) break; // 2. 更新節點狀態為已拜訪 visited[cur] = true // 3. 更新未拜訪節點與起始點的最短距離 for (let j = 1 ; j < end + 1 ; j++) { if(!visited[j] && grid[cur][j] != Number.MAX_VALUE && grid[cur][j] + minDist[cur] < minDist[j] ) { minDist[j] = grid[cur][j] + minDist[cur] } } } return minDist[end] === Number.MAX_VALUE ? -1 : minDist[end] } async function main() { // 輸入 const rl = require('readline').createInterface({ input: process.stdin }) const iter = rl[Symbol.asyncIterator]() const readline = async () => (await iter.next()).value const [n, m] = (await readline()).split(" ").map(Number) const grid = Array.from({length: n + 1}, () => Array.from({length:n + 1}, () => Number.MAX_VALUE)) for (let i = 0 ; i < m ; i++) { const [s, e, w] = (await readline()).split(" ").map(Number) grid[s][e] = w } // dijkstra const result = dijkstra(grid, 1, n) // 輸出 console.log(result) } main() ``` ### TypeScript ### PhP ### Swift ### Scala ### C# ### Dart ### C