方向一——题目解析:删除路径后的最短路问题(最短路径算法)
今天来看一看最短路径算法,最短路径算法主要有三种: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。
关键要素:
-
坐标与距离: 每个点的位置给定了 (𝑥ᵢ, 𝑦ᵢ) 坐标,通过这些坐标我们可以计算任意两点之间的距离,距离的计算公式为: [ d_{ij} = \sqrt{(x_i - x_j)^2 + (y_i - y_j)^2} ]
-
最短路径问题: 这实际上是一个标准的图论问题,在图中每个点是一个节点,每一对点之间的连通性(除去删除的直接路径)可以表示为图的边,边的权值为两点之间的欧几里得距离。我们需要从起点 s 出发,计算到终点 t 的最短路径,路径中不可以经过删除的边。
二、解题思路
前面已经说过,使用 Dijkstra 算法 来解决这个问题。步骤:
-
计算两点之间的欧几里得距离: 这一步是问题的基础,给定两个点的坐标 (𝑥ᵢ, 𝑦ᵢ),我们可以根据欧几里得公式计算出它们之间的距离。
-
构建图的邻接矩阵: 为了方便存储和查找点之间的距离,构建一个邻接矩阵
dist,其中dist[i][j]表示从点 i 到点 j 的距离。对于每一对不同的点(i, j),如果 i != j,就计算并存储它们之间的欧几里得距离。 -
Dijkstra 算法: 使用 Dijkstra 算法从起点 s 开始,求解到终点 t 的最短路径。需要特别注意的是,题目要求删除了从点 s 到点 t 的直接路径,所以在 Dijkstra 算法中我们需要跳过这个边,不允许走从 s 到 t 的直接路径。
-
循环条件: 由于 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')
主要知识点(敲黑板):
-
欧几里得距离计算: 通过欧几里得公式计算两点之间的距离,为图的边权。
-
Dijkstra算法: 用于计算单源最短路径,核心思想是每次选择当前已知最短路径的节点进行扩展,直到找到目标节点或所有节点都已访问。
-
优先队列(堆): Dijkstra算法中,使用优先队列来存储当前节点及其距离,保证每次弹出的是当前最短路径的节点,进行贪心策略。
-
图的邻接矩阵: 用二维数组存储每两个点之间的距离,便于计算与更新。
四、总结
本题考察了如何使用 Dijkstra 算法计算图中两个节点之间的最短路径,要注意如何处理路径删除的情况。关键在于如何构建图,正确地计算两点之间的欧几里得距离,并在 Dijkstra 算法中跳过被删除的路径。