AI 刷题349 最短路径问题 题解 | 豆包MarsCode AI刷题

57 阅读5分钟

最短路径问题:寻找通过其他点绕行的最短路线

问题描述

在一个平面地图上标记了若干个点,每个点都有其二维坐标。通常情况下,任意两点之间都存在一条直线路径,其长度等于两点间的欧几里得距离。现在,由于某些原因,起点和终点之间的直接路径不可用。我们需要计算从起点到终点,允许通过其他点绕行,但不能通过直接的起终点路径的最短距离。

核心思路

本问题的核心在于使用图论中的最短路径算法来解决,具体而言,使用了迪杰斯特拉(Dijkstra)算法。算法的主要难点在于处理特殊情况,即直接从起点到终点的路径不可用。这要求我们在算法执行过程中,避免通过该直线路径。

滑动窗口解析

滑动窗口算法:对于每个点,计算与其他所有点之间的距离,并存储这些值。使用一个优先队列(最小堆)来维护当前未处理的最短路径。

字符替换计数:在处理过程中,我们确保不通过直接的起点至终点路径,这可以通过在遍历邻接点时加以判断来实现。

计算并更新最大长度:通过维护一个距离数组,记录从起点到每一个点的最短距离,从而找到从起点到终点的最短路径。

算法详解

在实现时,首先初始化所有点之间的距离矩阵。接着,通过Dijkstra算法,从起点开始,更新通过各点到达其他点的最短路径长度。关键在于,每次从优先队列中取出当前最短路径的点时,跳过那些不符合条件的路径(即直接从起点到终点的路径)。

复杂度分析

  • 时间复杂度:主要消耗在两个方面,一是初始化距离矩阵,其复杂度为 O(n2)O(n^2)O(n2),二是Dijkstra算法本身,复杂度为 O((n2+n)log⁡n)O((n^2 + n) \log n)O((n2+n)logn),因为每个节点都可能进出优先队列。
  • 空间复杂度:主要消耗在存储距离矩阵和优先队列,总体为 O(n2)O(n^2)O(n2)。

Java代码

import java.util.*;

public class Main {
    public static String solution(int n, int s, int t, int[] x, int[] y) {
        // 计算两点之间的欧几里得距离
        double calculateDistance(int x1, int y1, int x2, int y2) {
            return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
        }

        // 构建邻接矩阵
        double[][] dist = new double[n][n];
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (i != j) {
                    dist[i][j] = calculateDistance(x[i], y[i], x[j], y[j]);
                }
            }
        }

        // Dijkstra算法实现
        double dijkstra(int start, int end) {
            // 如果起点等于终点,我们需要找到一个最小的环
            if (start == end) {
                double minCycle = Double.POSITIVE_INFINITY;
                // 尝试通过每个中间点构建一个环
                for (int mid = 0; mid < n; mid++) {
                    if (mid != start - 1) {
                        // 从起点到中间点,再返回起点的距离
                        double cycleDist = dist[start - 1][mid] + dist[mid][start - 1];
                        minCycle = Math.min(minCycle, cycleDist);
                    }
                }
                return minCycle;
            }

            // 初始化距离数组和访问标记
            double[] distances = new double[n];
            Arrays.fill(distances, Double.POSITIVE_INFINITY);
            distances[start - 1] = 0;
            PriorityQueue<double[]> pq = new PriorityQueue<>(Comparator.comparingDouble(a -> a[0]));
            pq.add(new double[]{0, start - 1});
            Set<Integer> visited = new HashSet<>();

            while (!pq.isEmpty()) {
                double[] curr = pq.poll();
                double d = curr[0];
                int currNode = (int) curr[1];

                if (visited.contains(currNode)) {
                    continue;
                }

                visited.add(currNode);

                if (currNode == end - 1) {
                    return d;
                }

                // 遍历所有相邻节点
                for (int nextNode = 0; nextNode < n; nextNode++) {
                    // 跳过已访问的节点和不允许的直接路径
                    if (visited.contains(nextNode) || (currNode == s - 1 && nextNode == t - 1) || (currNode == t - 1 && nextNode == s - 1)) {
                        continue;
                    }

                    double newDist = d + dist[currNode][nextNode];

                    if (newDist < distances[nextNode]) {
                        distances[nextNode] = newDist;
                        pq.add(new double[]{newDist, nextNode});
                    }
                }
            }

            return Double.POSITIVE_INFINITY;
        }

        // 计算最短路径并格式化结果
        double result = dijkstra(s, t);
        return String.format("%.2f", result);
    }

    public static void main(String[] args) {
        // 原有测试用例
        System.out.println(solution(5, 1, 5, new int[]{17253, 25501, 28676, 30711, 18651}, new int[]{15901, 15698, 32041, 11015, 9733}).equals("17333.65"));
        System.out.println(solution(4, 2, 4, new int[]{5000, 12000, 8000, 14000}, new int[]{3000, 9000, 1000, 4000}).equals("15652.48"));
        System.out.println(solution(6, 3, 6, new int[]{20000, 22000, 24000, 26000, 28000, 30000}, new int[]{15000, 13000, 11000, 17000, 19000, 21000}).equals("11772.70"));

        // 新增测试用例:起点终点相同的情况
        System.out.println(solution(10, 2, 2, new int[]{11, 3, 5, 6, 2, 4, 15, 14, 16, 8}, new int[]{5, 8, 7, 14, 8, 10, 5, 4, 2, 9}).equals("2.00"));
    }
}

实际应用

该算法不仅适用于理论中的最短路径问题,也可以扩展到现实世界中的路线规划,特别是在某些路径不可用或存在临时维修的情况下。例如,在交通导航系统中,经常需要绕过某些因交通事故或施工关闭的路段。

思考与挑战

尽管Dijkstra算法非常有效,但在节点数量非常大时,其性能可能会受到影响。一个可能的改进方案是采用更高效的算法,如A*或者是Floyd-Warshall算法,后者可以同时处理所有点对的最短路径问题,特别适用于点数量不是非常大,但需要频繁查询任意两点间最短路径的场景。

总结

本问题通过将地理坐标映射为图论中的点和边,并利用图算法解决实际问题,展示了数学工具在解决现实问题中的强大能力。对于计算机科学和地理信息系统等领域的学者和专业人士来说,理解并掌握这些算法是必不可少的。