题目描述
在一个二维平面上,小F探索一个包含 个点的地图。每个点都有一个对应的二维坐标 ,其中 表示第 个点。起点为第 个点,终点为第 个点。原本,所有点之间都有一条线段连接,表示这些点之间是可以通行的。线段的长度为欧几里得距离,但是由于一些意外,起点 和终点 之间的直接通行路径被删除了。小F希望你帮助他计算从起点 到终点 的最短路径,可以经过其他点,但不能直接通过删除的路径。
输入格式
- 整数 ():地图上的点的数量。
- 整数 ():起点编号。
- 整数 ():终点编号。
- 整数数组 和 (每个数组长度为 ),分别表示每个点的 坐标和 坐标。
输出格式
输出从起点 到终点 的最短路径的长度,四舍五入保留两位小数。
测试样例
输入:
n = 5, s = 1, t = 5
x = [17253, 25501, 28676, 30711, 18651]
y = [15901, 15698, 32041, 11015, 9733]
输出:
17333.65
思路分析
这个问题是一个典型的最短路径问题,但是由于限制条件中的“删除的路径”,我们需要对图中的某些边进行屏蔽处理。最短路径算法如 Dijkstra 可以有效解决这一类问题。我们将采用以下步骤来实现这一问题:
-
计算欧几里得距离: 我们需要计算两个点之间的欧几里得距离,公式为: [ d = \sqrt{(x_i - x_j)^2 + (y_i - y_j)^2} ] 这将是图中两点之间的边权重。
-
构建邻接矩阵: 对于每一对点,我们计算它们之间的距离,并存储在邻接矩阵中。
-
修改 Dijkstra 算法: 标准的 Dijkstra 算法会寻找从起点到终点的最短路径,但我们需要对图进行特殊处理。删除的路径 和 需要从图中排除,即不允许经过这条边。
-
使用优先队列: 使用最小堆(优先队列)来实现 Dijkstra 算法,以优化计算过程,减少时间复杂度。
-
返回结果: 最终计算出最短路径后,我们将其四舍五入保留两位小数并输出。
好的!在接下来的代码讲解中,我会将每个部分的功能与其对应的代码片段进行拆分,帮助更好地理解每个步骤如何实现。
代码分析
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 算法解决了一个典型的最短路径问题,其中要考虑到特定路径的删除限制。通过构建邻接矩阵和合理使用优先队列,算法能够高效地计算从起点到终点的最短路径,并正确处理删除路径的限制。