青训营X豆包MarsCode 技术训练营第九篇——删除路径后的最短路问题(题解与总结)| 豆包MarsCode AI 刷题

168 阅读7分钟

方向一——题目解析:删除路径后的最短路问题(最短路径算法)

今天来看一看最短路径算法,最短路径算法主要有三种:Dijkstra 算法,Bellman-Ford 算法,Floyd-Warshall 算法。

Dijkstra算法

是一个用于加权图的贪心算法,适用于图中边的权值均为非负数的情况。它的基本思想是通过逐步扩展最短路径集合,来找到源点到其他所有点的最短路径。

Bellman-Ford算法

是一种广泛用于求解有负权边图的最短路径问题的算法。它的基本思想是通过逐步松弛所有边,来计算出从源节点到其他所有节点的最短路径。

Floyd-Warshall算法

是一种计算图中所有节点对之间最短路径的动态规划算法。它的基本思想是通过逐步考虑中间节点来更新最短路径。

本题主要使用的是Dijkstra算法

问题描述

小F正在探索一个有n个点的地图,其中每个点都有对应的二维坐标 (𝑥𝑖,𝑦𝑖)(xi​,yi​)。起点是第𝑠s个点,终点是第𝑡t个点,原本所有点之间都有一条线段,表示可以通行,并且长度为欧几里得距离。但是由于某些意外,起点𝑠s和终点𝑡t之间的直接通行路径被删除了。小F希望你帮忙计算从𝑠s到𝑡t的最短路径,允许经过其他点,但不能直接通过删除的那条线。

需要注意的是,点 𝑖,𝑗i,j 之间的欧几里得距离计算公式为:

sqrt((x_i - x_j)^2 + (y_i - y_j)^2)

你需要输出从起点s到终点t的最短距离,结果需要四舍五入到小数点后两位。


测试样例

样例1:

输入:n = 5 ,s = 1 ,t = 5,x = [17253, 25501, 28676, 30711, 18651],y = [15901, 15698, 32041, 11015, 9733]
输出:'17333.65'

样例2:

输入:n = 4 ,s = 2 ,t = 4,x = [5000, 12000, 8000, 14000],y = [3000, 9000, 1000, 4000]
输出:'15652.48'

样例3:

输入:n = 6 ,s = 3 ,t = 6,x = [20000, 22000, 24000, 26000, 28000, 30000],y = [15000, 13000, 11000, 17000, 19000, 21000]
输出:'11772.70'

一、题目分析

小F的目标是计算从起点(第s个点)到终点(第t个点)的最短路径,要求路径不能直接经过已经删除的连通路径,即不能直接从点s到点t。

关键要素:
  1. 坐标与距离: 每个点的位置给定了 (𝑥ᵢ, 𝑦ᵢ) 坐标,通过这些坐标我们可以计算任意两点之间的距离,距离的计算公式为: [ d_{ij} = \sqrt{(x_i - x_j)^2 + (y_i - y_j)^2} ]

  2. 最短路径问题: 这实际上是一个标准的图论问题,在图中每个点是一个节点,每一对点之间的连通性(除去删除的直接路径)可以表示为图的边,边的权值为两点之间的欧几里得距离。我们需要从起点 s 出发,计算到终点 t 的最短路径,路径中不可以经过删除的边。

二、解题思路

前面已经说过,使用 Dijkstra 算法 来解决这个问题。步骤:

  1. 计算两点之间的欧几里得距离: 这一步是问题的基础,给定两个点的坐标 (𝑥ᵢ, 𝑦ᵢ),我们可以根据欧几里得公式计算出它们之间的距离。

  2. 构建图的邻接矩阵: 为了方便存储和查找点之间的距离,构建一个邻接矩阵 dist,其中 dist[i][j] 表示从点 i 到点 j 的距离。对于每一对不同的点 (i, j),如果 i != j,就计算并存储它们之间的欧几里得距离。

  3. Dijkstra 算法: 使用 Dijkstra 算法从起点 s 开始,求解到终点 t 的最短路径。需要特别注意的是,题目要求删除了从点 s 到点 t 的直接路径,所以在 Dijkstra 算法中我们需要跳过这个边,不允许走从 s 到 t 的直接路径。

  4. 循环条件: 由于 Dijkstra 算法是一个贪心算法,通常会通过优先队列来更新每个节点的最短路径。我们需要确保在更新最短路径时跳过删除的直接路径。

三、代码详解

利用 Dijkstra 算法计算最短路径(python):

import math
from heapq import heappush, heappop

def solution(n: int, s: int, t: int, x: list, y: list) -> str:
    # 计算两点之间的欧几里得距离
    def calculate_distance(x1, y1, x2, y2):
        return math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2)
    
    # 构建邻接矩阵
    dist = [[0] * n for _ in range(n)]
    for i in range(n):
        for j in range(n):
            if i != j:
                dist[i][j] = calculate_distance(x[i], y[i], x[j], y[j])
    
    # Dijkstra算法实现
    def dijkstra(start, end):
        # 如果起点等于终点,我们需要找到一个最小的环
        if start == end:
            min_cycle = float('inf')
            # 尝试通过每个中间点构建一个环
            for mid in range(n):
                if mid != start-1:
                    # 从起点到中间点,再返回起点的距离
                    cycle_dist = dist[start-1][mid] + dist[mid][start-1]
                    min_cycle = min(min_cycle, cycle_dist)
            return min_cycle
            
        # 初始化距离数组和访问标记
        distances = [float('inf')] * n
        distances[start-1] = 0
        pq = [(0, start-1)]
        visited = set()
        
        while pq:
            d, curr = heappop(pq)
            
            if curr in visited:
                continue
                
            visited.add(curr)
            
            if curr == end-1:
                return d
            
            # 遍历所有相邻节点
            for next_node in range(n):
                # 跳过已访问的节点和不允许的直接路径
                if next_node in visited or (curr == s-1 and next_node == t-1) or (curr == t-1 and next_node == s-1):
                    continue
                
                new_dist = d + dist[curr][next_node]
                
                if new_dist < distances[next_node]:
                    distances[next_node] = new_dist
                    heappush(pq, (new_dist, next_node))
        
        return float('inf')
    
    # 计算最短路径并格式化结果
    result = dijkstra(s, t)
    return "{:.2f}".format(result)

if __name__ == '__main__':
    # 原有测试用例
    print(solution(5, 1, 5, [17253, 25501, 28676, 30711, 18651], 
                   [15901, 15698, 32041, 11015, 9733]) == '17333.65')
    print(solution(4, 2, 4, [5000, 12000, 8000, 14000], 
                   [3000, 9000, 1000, 4000]) == '15652.48')
    print(solution(6, 3, 6, [20000, 22000, 24000, 26000, 28000, 30000], 
                   [15000, 13000, 11000, 17000, 19000, 21000]) == '11772.70')
    
    # 新增测试用例:起点终点相同的情况
    print(solution(10, 2, 2, [11,3,5,6,2,4,15,14,16,8], 
                   [5,8,7,14,8,10,5,4,2,9]) == '2.00')

主要知识点(敲黑板):

  1. 欧几里得距离计算: 通过欧几里得公式计算两点之间的距离,为图的边权。

  2. Dijkstra算法: 用于计算单源最短路径,核心思想是每次选择当前已知最短路径的节点进行扩展,直到找到目标节点或所有节点都已访问。

  3. 优先队列(堆): Dijkstra算法中,使用优先队列来存储当前节点及其距离,保证每次弹出的是当前最短路径的节点,进行贪心策略。

  4. 图的邻接矩阵: 用二维数组存储每两个点之间的距离,便于计算与更新。

四、总结

本题考察了如何使用 Dijkstra 算法计算图中两个节点之间的最短路径,要注意如何处理路径删除的情况。关键在于如何构建图,正确地计算两点之间的欧几里得距离,并在 Dijkstra 算法中跳过被删除的路径。