MarsCode AI 刷题思路解析:349. (难) 删除路径后的最短路问题 | 豆包MarsCode AI刷题

61 阅读7分钟

题目描述

在一个二维平面上,小F探索一个包含 nn 个点的地图。每个点都有一个对应的二维坐标 (xi,yi)(x_i, y_i),其中 ii 表示第 ii 个点。起点为第 ss 个点,终点为第 tt 个点。原本,所有点之间都有一条线段连接,表示这些点之间是可以通行的。线段的长度为欧几里得距离,但是由于一些意外,起点 ss 和终点 tt 之间的直接通行路径被删除了。小F希望你帮助他计算从起点 ss 到终点 tt 的最短路径,可以经过其他点,但不能直接通过删除的路径。

输入格式

  • 整数 nn (2n1002 \leq n \leq 100):地图上的点的数量。
  • 整数 ss (1sn1 \leq s \leq n):起点编号。
  • 整数 tt (1tn1 \leq t \leq n):终点编号。
  • 整数数组 xxyy(每个数组长度为 nn),分别表示每个点的 xx 坐标和 yy 坐标。

输出格式

输出从起点 ss 到终点 tt 的最短路径的长度,四舍五入保留两位小数。

测试样例

输入:

n = 5, s = 1, t = 5
x = [17253, 25501, 28676, 30711, 18651]
y = [15901, 15698, 32041, 11015, 9733]

输出:

17333.65

思路分析

这个问题是一个典型的最短路径问题,但是由于限制条件中的“删除的路径”,我们需要对图中的某些边进行屏蔽处理。最短路径算法如 Dijkstra 可以有效解决这一类问题。我们将采用以下步骤来实现这一问题:

  1. 计算欧几里得距离: 我们需要计算两个点之间的欧几里得距离,公式为: [ d = \sqrt{(x_i - x_j)^2 + (y_i - y_j)^2} ] 这将是图中两点之间的边权重。

  2. 构建邻接矩阵: 对于每一对点,我们计算它们之间的距离,并存储在邻接矩阵中。

  3. 修改 Dijkstra 算法: 标准的 Dijkstra 算法会寻找从起点到终点的最短路径,但我们需要对图进行特殊处理。删除的路径 (s,t)(s, t)(t,s)(t, s) 需要从图中排除,即不允许经过这条边。

  4. 使用优先队列: 使用最小堆(优先队列)来实现 Dijkstra 算法,以优化计算过程,减少时间复杂度。

  5. 返回结果: 最终计算出最短路径后,我们将其四舍五入保留两位小数并输出。

好的!在接下来的代码讲解中,我会将每个部分的功能与其对应的代码片段进行拆分,帮助更好地理解每个步骤如何实现。

代码分析

1. 计算两点之间的欧几里得距离

我们需要计算两个点之间的欧几里得距离,这是图中每对节点之间的边权重。欧几里得距离的公式如下:

[ d = \sqrt{(x_i - x_j)^2 + (y_i - y_j)^2} ]

代码片段:

# 计算两点之间的欧几里得距离
def calculate_distance(x1, y1, x2, y2):
    return math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2)
  • 功能calculate_distance 函数接受四个参数,分别是两个点的 (x) 和 (y) 坐标,返回这两个点之间的欧几里得距离。
  • 原理:使用标准的欧几里得距离公式,计算两个点之间的直线距离。

2. 构建邻接矩阵

邻接矩阵 dist 用来表示所有点之间的距离。矩阵中的每个元素 (dist[i][j]) 表示点 (i) 和点 (j) 之间的距离。对每一对点,我们都计算并存储它们之间的欧几里得距离。

代码片段:

# 构建邻接矩阵
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])
  • 功能:创建一个 (n \times n) 的矩阵 dist,并使用双重循环填充矩阵中的每个值,表示点之间的距离。
  • 原理:通过嵌套循环遍历所有点对,调用 calculate_distance 函数计算每对点的距离,并将结果存入邻接矩阵 dist 中。

3. Dijkstra 算法

Dijkstra 算法用于在图中寻找从起点到终点的最短路径。其基本原理是不断选择距离起点最近的未访问点,然后更新所有相邻节点的最短距离。

3.1. 起点和终点相同的特殊情况

在某些情况下,起点和终点可能是同一个点。如果是这样,我们需要考虑图中是否存在环路来形成最短路径。

代码片段:

# 如果起点等于终点,我们需要找到一个最小的环
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
  • 功能:如果起点和终点相同,算法计算通过一个中间节点回到起点的最小环路。
  • 原理:遍历所有其他节点,计算通过某个节点构成的回路的距离,选择最小的回路作为最短路径。

3.2. 初始化 Dijkstra 算法的数据结构

我们为 Dijkstra 算法初始化一个 distances 数组来存储每个节点的最短路径值,并使用优先队列(最小堆)来获取当前距离最小的节点。

代码片段:

# 初始化距离数组和访问标记
distances = [float('inf')] * n
distances[start-1] = 0
pq = [(0, start-1)]
visited = set()
  • 功能:初始化 distances 数组为无穷大,表示所有节点的初始最短距离为无限大。起点的距离为 0。
  • 原理:使用优先队列 pq 来按距离升序存储节点,优先队列保证每次弹出的是当前最短路径的节点。visited 集合用于标记已经访问过的节点。

3.3. 使用优先队列遍历所有节点

在 Dijkstra 算法的主循环中,我们不断从优先队列中取出距离最小的节点,并更新其相邻节点的距离。

代码片段:

while pq:
    d, curr = heappop(pq)
    
    if curr in visited:
        continue
    
    visited.add(curr)
    
    if curr == end-1:
        return d
  • 功能:不断从优先队列 pq 中取出距离最小的节点 curr,并检查是否已访问。如果是终点节点 end,则返回当前的最短路径。
  • 原理:优先队列保证每次弹出的是当前未访问的距离最小的节点。我们检查该节点是否是终点,如果是则返回其最短路径距离。

3.4. 更新相邻节点的距离

对于当前节点的每个相邻节点,如果该节点没有被访问过,并且不是被删除的路径,我们就计算从当前节点到相邻节点的新距离,并更新该节点的最短距离。

代码片段:

# 遍历所有相邻节点
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))
  • 功能:对于当前节点 curr 的每个邻居节点 next_node,检查该节点是否已访问过,是否是被删除的路径((s) 到 (t) 或 (t) 到 (s) 的路径),如果满足条件则跳过该节点。否则计算新距离并更新。
  • 原理:每次通过更新邻居节点的距离,优化从起点到各个节点的最短路径。优先队列 pq 保证了每次选择的是当前路径最短的节点。

4. 输出结果

Dijkstra 算法运行结束后,我们得到了从起点到终点的最短路径长度。最后,我们将结果格式化为保留两位小数并返回。

代码片段:

# 计算最短路径并格式化结果
result = dijkstra(s, t)
return "{:.2f}".format(result)
  • 功能:调用 dijkstra 函数获取最短路径,然后使用格式化方法 "{:.2f}".format(result) 保留两位小数输出。
  • 原理:返回的结果已经是最短路径距离,我们通过格式化确保输出符合题目要求的精度。

总结

这个问题通过修改 Dijkstra 算法解决了一个典型的最短路径问题,其中要考虑到特定路径的删除限制。通过构建邻接矩阵和合理使用优先队列,算法能够高效地计算从起点到终点的最短路径,并正确处理删除路径的限制。