1. 题目背景与核心需求解析
这道来自京东2026年春招的编程题"星际快递"描述了一个典型的图论优化问题。题目设定在星际快递网络中,我们需要计算从起点星球到终点星球的最短运输时间。这类问题在实际工程中非常常见,比如物流路径规划、网络路由优化等场景。
1.1 问题形式化描述
题目给出:
- n个星球(节点)和m条星际航线(边)
- 每条航线有固定的运输时间(边权值)
- 可能存在单向航线(有向边)
- 要求找出从起点s到终点t的最短运输时间
这本质上是一个带权有向图的最短路径问题,属于经典的图论算法应用场景。
1.2 输入输出规范分析
输入格式通常为:
- 第一行:n m s t(星球数、航线数、起点、终点)
- 后续m行:u v w(从u到v的航线,耗时w)
输出要求:
- 单个整数表示最短时间
- 若不可达则输出-1
示例输入:
code复制3 3 1 3
1 2 2
2 3 1
1 3 4
对应输出:
code复制3
2. 算法选型与复杂度分析
2.1 常见最短路径算法对比
针对这类问题,我们有几种经典算法可选:
| 算法 | 时间复杂度 | 适用场景 | 本题适用性 |
|---|---|---|---|
| Dijkstra | O((V+E)logV) | 非负权图 | ★★★★★ |
| Bellman-Ford | O(VE) | 含负权边 | ★★☆☆☆ |
| Floyd-Warshall | O(V³) | 全源最短路径 | ★☆☆☆☆ |
| SPFA | 最坏O(VE) | 稀疏图负权 | ★★★☆☆ |
2.2 Dijkstra算法详解
本题明确航线时间为正数,因此Dijkstra是最佳选择。其核心思想是:
- 初始化:起点距离为0,其他为∞
- 每次从优先队列取出当前距离最小的节点
- 松弛操作:更新邻居节点的最短距离
- 重复直到队列为空或到达终点
python复制import heapq
def dijkstra(n, edges, s, t):
graph = [[] for _ in range(n+1)]
for u, v, w in edges:
graph[u].append((v, w))
dist = [float('inf')] * (n+1)
dist[s] = 0
heap = [(0, s)]
while heap:
d, u = heapq.heappop(heap)
if u == t:
return d
if d > dist[u]:
continue
for v, w in graph[u]:
if dist[v] > dist[u] + w:
dist[v] = dist[u] + w
heapq.heappush(heap, (dist[v], v))
return -1
2.3 算法优化技巧
- 优先队列实现:Python中使用heapq模块,C++用priority_queue,Java用PriorityQueue
- 提前终止:当取出终点时可立即返回结果
- 懒惰删除:不立即删除队列中的旧值,通过距离比较过滤
- 邻接表存储:比邻接矩阵更节省空间
3. 多语言实现详解
3.1 Java实现
java复制import java.util.*;
public class StarExpress {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(), m = sc.nextInt();
int s = sc.nextInt(), t = sc.nextInt();
List<List<int[]>> graph = new ArrayList<>();
for (int i = 0; i <= n; i++) {
graph.add(new ArrayList<>());
}
for (int i = 0; i < m; i++) {
int u = sc.nextInt(), v = sc.nextInt(), w = sc.nextInt();
graph.get(u).add(new int[]{v, w});
}
int[] dist = new int[n+1];
Arrays.fill(dist, Integer.MAX_VALUE);
dist[s] = 0;
PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[1] - b[1]);
pq.offer(new int[]{s, 0});
while (!pq.isEmpty()) {
int[] curr = pq.poll();
int u = curr[0], d = curr[1];
if (u == t) {
System.out.println(d);
return;
}
if (d > dist[u]) continue;
for (int[] edge : graph.get(u)) {
int v = edge[0], w = edge[1];
if (dist[v] > dist[u] + w) {
dist[v] = dist[u] + w;
pq.offer(new int[]{v, dist[v]});
}
}
}
System.out.println(-1);
}
}
3.2 C++实现
cpp复制#include <iostream>
#include <vector>
#include <queue>
#include <climits>
using namespace std;
int main() {
int n, m, s, t;
cin >> n >> m >> s >> t;
vector<vector<pair<int, int>>> graph(n+1);
for (int i = 0; i < m; ++i) {
int u, v, w;
cin >> u >> v >> w;
graph[u].emplace_back(v, w);
}
vector<int> dist(n+1, INT_MAX);
dist[s] = 0;
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
pq.emplace(0, s);
while (!pq.empty()) {
auto [d, u] = pq.top();
pq.pop();
if (u == t) {
cout << d << endl;
return 0;
}
if (d > dist[u]) continue;
for (auto [v, w] : graph[u]) {
if (dist[v] > dist[u] + w) {
dist[v] = dist[u] + w;
pq.emplace(dist[v], v);
}
}
}
cout << -1 << endl;
return 0;
}
3.3 Python实现
python复制import heapq
def main():
import sys
input = sys.stdin.read
data = input().split()
idx = 0
n = int(data[idx]); idx +=1
m = int(data[idx]); idx +=1
s = int(data[idx]); idx +=1
t = int(data[idx]); idx +=1
graph = [[] for _ in range(n+1)]
for _ in range(m):
u = int(data[idx]); idx +=1
v = int(data[idx]); idx +=1
w = int(data[idx]); idx +=1
graph[u].append((v, w))
dist = [float('inf')] * (n+1)
dist[s] = 0
heap = [(0, s)]
while heap:
d, u = heapq.heappop(heap)
if u == t:
print(d)
return
if d > dist[u]:
continue
for v, w in graph[u]:
if dist[v] > dist[u] + w:
dist[v] = dist[u] + w
heapq.heappush(heap, (dist[v], v))
print(-1)
if __name__ == "__main__":
main()
4. 边界条件与测试用例设计
4.1 常见边界情况
- 起点即终点:s == t,应输出0
- 不可达情况:没有路径连接s和t,输出-1
- 自环边:u == v的边,通常不影响正确性
- 重边:多条u→v的边,应取最小权值
- 大规模数据:测试算法效率,如n=1e5
4.2 测试用例示例
text复制# Case 1: 正常情况
3 3 1 3
1 2 2
2 3 1
1 3 4
Expect: 3
# Case 2: 不可达
3 2 1 3
1 2 1
2 1 2
Expect: -1
# Case 3: 起点即终点
3 2 2 2
1 2 1
2 3 2
Expect: 0
# Case 4: 重边取最小
3 3 1 3
1 3 5
1 2 1
2 3 1
Expect: 2
5. 算法扩展与变种思考
5.1 问题变种示例
- 第K短路径:使用Yen's算法或Eppstein's算法
- 边权为浮点数:Dijkstra同样适用
- 时间依赖边权:需要更复杂的算法
- 多目标优化:同时考虑时间和成本
5.2 实际工程应用
- 物流路径规划:考虑运输时间和成本
- 网络路由:寻找最低延迟路径
- 游戏AI寻路:地图导航系统
- 社交网络分析:计算用户关联度
提示:在面试中遇到类似问题时,务必先与面试官确认图的特性(是否有负权、是否需要处理重边等),这对算法选择至关重要。
6. 性能优化与工程实践
6.1 大数据量处理
当星球数量n很大时(如1e5级别):
- 使用更高效的优先队列实现(如Fibonacci堆)
- 考虑使用双向Dijkstra搜索
- 对图进行预处理或分区
6.2 内存优化技巧
- 使用紧凑的邻接表存储
- 对于稀疏图,使用CSR格式存储
- 在C++中预分配内存避免频繁扩容
6.3 常见错误排查
- 无限循环:检查优先队列的更新逻辑
- 错误结果:验证松弛条件的实现
- 性能问题:检查数据结构选择是否合理
- 边界错误:处理节点编号从0还是1开始
7. 在线评测注意事项
-
输入读取优化:
- C++使用ios::sync_with_stdio(false)
- Java使用BufferedReader
- Python考虑使用sys.stdin.read
-
输出格式:
- 确保最后输出换行符
- 不要输出多余空格或文字
-
时间限制:
- Python可能需要在OJ上选择PyPy解释器
- 对于严格时间限制,可能需要优化常数因子
-
内存限制:
- 避免使用不必要的全局变量
- 及时释放不再需要的数据结构
在实际编码竞赛中,建议先写出基础Dijkstra实现,通过样例后再考虑优化。过早优化可能导致代码复杂化并引入错误。