#P2807. 第2题-地铁耗时最短的线路
-
1000ms
Tried: 4146
Accepted: 826
Difficulty: 4
所属公司 :
华为
时间 :2025年4月9日-暑期实习
-
算法标签>最短路算法
第2题-地铁耗时最短的线路
题解
题面描述
给定一个地铁线路网络,其中有N个站点(3≤N≤20)以及各个相邻站点之间的乘坐时间。输入中:
- 第一行为站点总数N;
- 第二行为乘客的出发站s和到达站t;
- 接下来的若干行每行给出两个站点和它们之间的乘坐时间,输入持续到出现结束符"0000"为止。
本题中地铁网络被视为无向图,即每条输入的边可以双向使用。保证输入中起点与终点之间存在唯一一条耗时最短的路径。要求程序输出由各站点名称组成、以空格分隔的完整最短路径线路。
解题思路
-
构图:
将输入数据构造为一个无向图。每个站点作为图中的节点,每条输入边对应两个方向的边(即u→v和v→u),边的权值均为乘坐时间w。 -
算法选择:
使用Dijkstra算法求解从出发站s到终点t的最短时间路径。由于各边权均为正值,Dijkstra能够高效地给出正确解。 -
路径还原:
在执行Dijkstra过程中记录每个节点的前驱节点,最终利用该前驱记录由终点回溯到起点,再反转得到完整的最短路径。
代码分析
-
变量定义:
- 站点总数用变量N表示;
- 出发站和到达站分别用s和t表示;
- 图采用邻接表形式存储(例如unordered_map、dict或HashMap),存储每个节点的邻边及对应乘坐时间。
-
无向边构造:
读入每条边数据时,分别添加u→v和v→u两个方向的边。 -
Dijkstra算法实现:
- 初始化距离映射dist(所有站点距离设为无穷大∞,起点s距离置为0),并构造前驱记录映射prev;
- 利用优先队列维护当前最短路径候选,更新相邻边时检查是否可以用更小的距离更新终点的距离,并记录前驱节点。
-
问题本质:
本题归结为图论中的单源最短路径问题,核心在于如何正确构造无向图以及利用$$Dijkstra$$算法计算最短距离并恢复最短路径。
C++ 参考代码
#include <iostream>
#include <sstream>
#include <vector>
#include <unordered_map>
#include <limits>
#include <queue>
#include <string>
#include <algorithm>
using namespace std;
// 定义边结构体,表示两个节点之间的乘坐时间
struct Edge {
char to; // 目的节点
int time; // 乘坐时间
};
int main(){
int N;
cin >> N; // 输入站点总数N
char start, end;
cin >> start >> end; // 输入出发站s和到达站t
// 构造无向图,使用邻接表存储,每个输入边添加两个方向的边
unordered_map<char, vector<Edge>> graph;
while(true){
string u_str;
cin >> u_str; // 如果遇到结束符"0000"则退出
if(u_str == "0000") break;
char u = u_str[0];
char v;
int t;
cin >> v >> t; // 读取两个站点和乘坐时间w
graph[u].push_back({v, t});
graph[v].push_back({u, t}); // 添加反向边
}
// 初始化距离映射和前驱映射
unordered_map<char, int> dist;
unordered_map<char, char> prev;
const int INF = numeric_limits<int>::max(); // 设定无穷大INF
// 确保图中每个涉及的节点都在距离映射中
for(auto &entry : graph){
dist[entry.first] = INF;
for(auto edge : entry.second){
if(dist.find(edge.to) == dist.end()){
dist[edge.to] = INF;
}
}
}
dist[start] = 0; // 起点距离置为0
// 定义优先队列,按照距离从小到大排序
using pii = pair<int, char>; // 表示(距离, 节点)
priority_queue<pii, vector<pii>, greater<pii>> pq;
pq.push({0, start});
// Dijkstra 算法核心循环
while(!pq.empty()){
auto [current_time, cur] = pq.top();
pq.pop();
if(current_time > dist[cur]) continue;
// 遍历当前节点的所有出边
for(auto edge : graph[cur]){
char next = edge.to;
int new_time = current_time + edge.time;
if(new_time < dist[next]){
dist[next] = new_time;
prev[next] = cur; // 记录前驱节点
pq.push({new_time, next});
}
}
}
// 使用前驱映射还原完整最短路径
vector<char> path;
for(char cur = end; cur != start; cur = prev[cur])
path.push_back(cur);
path.push_back(start);
reverse(path.begin(), path.end());
// 输出站点路径,站点之间用空格分隔
for(size_t i = 0; i < path.size(); i++){
cout << path[i];
if(i != path.size() - 1)
cout << " ";
}
cout << endl;
return 0;
}
Python 参考代码
import sys
import heapq
# 读取所有输入数据
lines = sys.stdin.read().strip().splitlines()
# 第一行:站点总数N
N = int(lines[0])
# 第二行:出发站s和到达站t
start, end = lines[1].split()
start = start.strip()
end = end.strip()
# 构造无向图,使用字典存储邻接边 (目的站, 乘坐时间)
graph = {}
i = 2
while i < len(lines):
if lines[i].strip() == "0000":
break
parts = lines[i].split()
u = parts[0].strip()
v = parts[1].strip()
t = int(parts[2])
# 添加 u -> v 的边
if u not in graph:
graph[u] = []
graph[u].append((v, t))
# 添加 v -> u 的边(无向图)
if v not in graph:
graph[v] = []
graph[v].append((u, t))
i += 1
# 初始化距离字典和前驱字典
INF = float('inf')
dist = {}
prev = {}
# 确保图中所有涉及的节点都在距离字典中
for u in graph:
if u not in dist:
dist[u] = INF
for (v, time) in graph[u]:
if v not in dist:
dist[v] = INF
dist[start] = 0
# 使用 heapq 实现优先队列,存储格式为 (距离, 节点)
pq = [(0, start)]
while pq:
current_time, cur = heapq.heappop(pq)
if current_time > dist[cur]:
continue
# 遍历当前节点的所有出边
for (next_node, w) in graph[cur]:
new_time = current_time + w
if new_time < dist[next_node]:
dist[next_node] = new_time
prev[next_node] = cur
heapq.heappush(pq, (new_time, next_node))
# 利用前驱字典回溯恢复完整最短路径
path = []
cur = end
while cur != start:
path.append(cur)
cur = prev[cur]
path.append(start)
path.reverse()
# 输出最短路径,站点之间空格分隔
print(" ".join(path))
Java 参考代码
import java.util.*;
import java.io.*;
public class MetroShortestPath {
// 定义边的类,包含目的站点和乘坐时间
static class Edge {
char to; // 目的站点
int time; // 乘坐时间
public Edge(char to, int time) {
this.to = to;
this.time = time;
}
}
// 用于优先队列中,存储(距离, 节点)对
static class Node implements Comparable<Node> {
char station;
int distance;
Node(char station, int distance) {
this.station = station;
this.distance = distance;
}
public int compareTo(Node other) {
return this.distance - other.distance;
}
}
public static void main(String[] args) throws Exception {
Scanner sc = new Scanner(System.in);
// 读取站点总数N
int N = sc.nextInt();
// 读取出发站s和到达站t
char start = sc.next().charAt(0);
char end = sc.next().charAt(0);
// 构造无向图,使用 HashMap 存储每个节点的邻接边,两条边表示无向边
Map<Character, List<Edge>> graph = new HashMap<>();
while (sc.hasNext()) {
String token = sc.next();
if (token.equals("0000"))
break;
char u = token.charAt(0);
char v = sc.next().charAt(0);
int t = sc.nextInt();
// 添加 u -> v 的边
if (!graph.containsKey(u)) {
graph.put(u, new ArrayList<>());
}
graph.get(u).add(new Edge(v, t));
// 添加 v -> u 的边(无向图)
if (!graph.containsKey(v)) {
graph.put(v, new ArrayList<>());
}
graph.get(v).add(new Edge(u, t));
}
// 初始化距离映射和前驱映射
Map<Character, Integer> dist = new HashMap<>();
Map<Character, Character> prev = new HashMap<>();
final int INF = Integer.MAX_VALUE;
// 确保所有涉及的节点均被初始化
for (Character u : graph.keySet()) {
dist.put(u, INF);
for (Edge edge : graph.get(u)) {
if (!dist.containsKey(edge.to)) {
dist.put(edge.to, INF);
}
}
}
dist.put(start, 0); // 出发站距离设为0
// 使用优先队列实现 Dijkstra 算法
PriorityQueue<Node> pq = new PriorityQueue<>();
pq.add(new Node(start, 0));
while (!pq.isEmpty()) {
Node curNode = pq.poll();
char cur = curNode.station;
int current_time = curNode.distance;
if (current_time > dist.get(cur))
continue;
if (graph.containsKey(cur)) {
for (Edge edge : graph.get(cur)) {
char next = edge.to;
int new_time = current_time + edge.time;
if (new_time < dist.get(next)) {
dist.put(next, new_time);
prev.put(next, cur);
pq.add(new Node(next, new_time));
}
}
}
}
// 利用前驱映射恢复完整最短路径
List<Character> path = new ArrayList<>();
for (char cur = end; cur != start; cur = prev.get(cur)) {
path.add(cur);
}
path.add(start);
Collections.reverse(path);
// 输出最短路径,各站点之间以空格分隔
StringBuilder sb = new StringBuilder();
for (int i = 0; i < path.size(); i++) {
sb.append(path.get(i));
if(i != path.size() - 1)
sb.append(" ");
}
System.out.println(sb.toString());
sc.close();
}
}
题目内容
大湾区某城市地铁线路非常密集,乘客很难一眼看出选择哪条线路乘型比较合适,为了解决这个问题,地铁公司希望你开发一个程序帮助乘客挑选合适的乘坐线路,使得乘坐时间最短,地铁公司可以提供的数据是各相邻站点之间的乘坐时间。
输入描述
第一行:N,站点总数,3<=N<=20.
第二行:乘客的出发和到达站点。
第三行起:相邻站点之间的乘坐时间,每对站点一行,站点名称是单个小写字母,站点名一定包括出发和到达站点,输入保证只有一个唯一解;
结束行:0000
输出描述
耗时最短的线路
样例1
输入
12
a e
a b 2
b c 2
c d 2
d e 2
f b 3
b g 3
g h 2
h i 3
j h 2
h e 3
e k 2
k l 4
0000
输出
a b c d e
说明
输入对应的地铁图如下:

线路а->b->c->d->e耗时最短
样例2
输入
12
f k
a b 2
b c 2
c d 2
d e 2
f b 3
b g 3
g h 2
h i 3
j h 2
h e 3
e k 2
k l 4
0000
输出
f b c d e k
说明
输入对应的地铁图如下:

f->b->c->d->e->K是耗时最短的线路