Dijkstra 算法——找到最短路径,但如何找到路径呢?

 时间:2025年 01月 19日
作者:小蒋聊技术
邮箱:wei_wei10@163.com
微信:wei_wei10
音频:喜马拉雅

        大家好,欢迎来到小蒋聊技术!我是你们的朋友—小蒋。在上期分享中,我们一起学习了 Dijkstra 算法,它帮助我们找到从一个点到其他所有点的 最短路径。你是不是觉得自己掌握了一个超级实用的算法,能够在复杂的道路图中轻松找到最短路径了呢?

        然而,在这次分享中,我们将深入探讨一个问题:虽然 Dijkstra 算法 可以准确计算出最短路径的距离,但如何具体知道从起点到终点的路径呢?也就是说,通过Dijkstra 算法如何不仅告诉你“最短距离是 15”,还能告诉你“从 A 到 D 的具体路径是 A → C → B → D”呢?

        本次分享的内容,小蒋将给你解答这个问题,带你走进 路径回溯 的世界,帮助你更全面地理解 Dijkstra 算法 的应用。

1. 回顾 Dijkstra 算法

        在深入讨论如何找到路径之前,我们先简单回顾一下 Dijkstra 算法 的核心概念。

        Dijkstra 算法 是一种用于 单源最短路径 的算法。它的主要作用是计算出从某个起始节点到图中所有其他节点的最短路径。

        我们已经知道,在 Dijkstra 算法 的执行过程中,会通过以下几步来确定每个节点到起点的最短路径:

  1. 初始化:为每个节点设置初始距离(除了起点距离为 0,其他都为无穷大),并将起点加入到优先队列中。
  2. 选择最短路径的节点:每次选择距离起点最近的节点(即当前未处理的节点中,距离最短的节点)。
  3. 更新邻居节点的距离:对于该节点的每个邻居节点,通过计算新的距离,看看是否可以更新它们的最短路径。
  4. 重复:直到所有节点的最短路径被处理完毕。

        通过这个过程,我们可以得到每个节点到起点的最短距离,但这并不能告诉我们 从起点到终点的具体路径

2. 如何找到具体的路径?

        在 Dijkstra 算法 中,计算最短路径时,我们会根据每个节点的最短距离来更新其邻居节点的距离。但是,Dijkstra 算法并没有记录每个节点是通过哪个前驱节点(即先到达该节点的节点)走到它的。这就意味着,尽管我们知道每个节点的最短路径长度,却不知道如何从起点一步步到达目标节点。

        为了解决这个问题,我们引入了 前驱节点数组(prev[])。这个数组记录了每个节点是从哪个节点到达的,通过回溯这个数组,我们可以重建出从起点到目标点的具体路径。

2.1 前驱节点数组的作用

        假设我们从起点 A 开始计算最短路径,当我们处理每个节点时,除了更新它的最短距离外,还会记录当前节点是通过哪个节点到达的。例如:

  • 如果节点 B 的最短路径是通过 A 到达的,那么我们会将 prev[B] 设为 A。
  • 如果节点 C 的最短路径是通过 B 到达的,那么我们会将 prev[C] 设为 B。

        通过这种方式,我们不仅能得到每个节点的最短路径距离,还能知道从哪个节点走到当前节点,从而在计算完成后回溯整个路径。

2.2 如何回溯路径?

一旦我们计算完成了 Dijkstra 算法,我们可以通过前驱节点数组来找出从起点到终点的路径。假设我们要从节点 A 到节点 D 找到路径,我们可以通过以下步骤:

  1. 查看终点 D 的前驱节点:如果 prev[D] = C,则说明 D 是通过 C 达到的。
  2. 查看 C 的前驱节点:如果 prev[C] = A,则说明 C 是通过 A 达到的。
  3. 重建路径:通过回溯前驱节点数组,我们可以得出路径为:A → C → D。

2.3 路径回溯示意

        假设我们在运行 Dijkstra 算法时,得到了如下的前驱节点数组(prev[]):

prev[A] = null
prev[B] = A
prev[C] = A
prev[D] = C

        这表示,从 A 到 B 和 C 都是直接到达的,而从 C 到 D 是最短路径。现在,我们可以从 D 开始回溯,逐步找出路径:

  • 从 D 开始,前驱节点是 C
  • 从 C 开始,前驱节点是 A
  • 从 A 开始,前驱节点是 null(表示起点)

所以,最终的路径就是:A → C → D。

3. 如何在代码中实现路径回溯?

        让我们看一个 Java 实现,将路径回溯的过程添加到 Dijkstra 算法 中。

import java.util.*;

class Dijkstra {
    public void findShortestPath(Graph graph, int source) {
        int[] dist = new int[graph.size()];
        Arrays.fill(dist, Integer.MAX_VALUE);
        dist[source] = 0;

        int[] prev = new int[graph.size()];
        Arrays.fill(prev, -1);  // 初始化为 -1,表示无前驱节点

        PriorityQueue<Node> pq = new PriorityQueue<>(Comparator.comparingInt(n -> n.distance));
        pq.add(new Node(source, 0));

        while (!pq.isEmpty()) {
            Node node = pq.poll();
            for (Edge edge : graph.getEdges(node)) {
                if (dist[edge.to] > dist[node.id] + edge.weight) {
                    dist[edge.to] = dist[node.id] + edge.weight;
                    prev[edge.to] = node.id;  // 记录前驱节点
                    pq.add(new Node(edge.to, dist[edge.to]));
                }
            }
        }

        // 路径回溯部分
        List<Integer> path = new ArrayList<>();
        for (int at = graph.size() - 1; at != -1; at = prev[at]) {
            path.add(at);
        }
        Collections.reverse(path);
        
        System.out.println("Shortest path: " + path);
    }
}

3.1 代码解析

  • 我们使用了一个数组 prev[] 来记录每个节点的前驱节点。
  • 在计算最短路径的过程中,每次更新距离时,都将当前节点的前驱节点存入 prev[]。
  • 最后,我们通过回溯 prev[] 数组来得到从起点到终点的具体路径,并通过列表 path[] 输出。

4. 总结

        今天,我们深入探讨了 Dijkstra 算法 中如何通过 路径回溯 找到最短路径的具体步骤。我们学会了如何引入前驱节点数组,记录每个节点的来源,从而重建从起点到终点的具体路径。

        通过这个过程,我们不仅能计算出最短路径的距离,还能够得到完整的路径,帮助我们在现实世界中导航。无论是旅行规划还是网络路由,Dijkstra 算法 都能派上大用场!

        好的,今天就先给大家分享这么多。小蒋我是你们的朋友,小蒋聊技术 ,我们下期再见!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小蒋聊技术

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值