别再死记硬背Dijkstra了!用C++邻接表+优先队列实现最短路径(附LeetCode实战)
·
别再死记硬背Dijkstra了!用C++邻接表+优先队列实现最短路径(附LeetCode实战)
在算法面试和工程实践中,Dijkstra算法是解决单源最短路径问题的经典选择。但很多开发者仍停留在邻接矩阵+暴力搜索的传统实现上,面对LeetCode等平台的大规模图数据时,往往遭遇性能瓶颈。本文将带你突破这一困境,掌握 邻接表存储 与 优先队列优化 的现代实现方案,并手把手解决LeetCode 743「网络延迟时间」问题。
1. 为什么需要优化传统Dijkstra实现?
1.1 邻接矩阵的局限性
邻接矩阵虽然直观,但在稀疏图中会浪费大量空间。例如处理10万个节点的社交网络图时:
| 存储方式 | 空间复杂度 | 10万节点所需空间 |
|---|---|---|
| 邻接矩阵 | O(V²) | 40GB |
| 邻接表 | O(V+E) | 约10MB |
// 邻接矩阵声明示例(不推荐)
vector<vector<int>> graph(V, vector<int>(V, INF));
1.2 暴力搜索的性能瓶颈
传统实现通过遍历所有节点寻找最小距离节点,导致O(V²)时间复杂度。当V超过1万时,算法响应时间将呈指数级增长:
// 低效的最小距离查找(典型面试错误写法)
int minDist = INF, u = -1;
for(int v = 0; v < V; ++v) {
if(!visited[v] && dist[v] < minDist) {
minDist = dist[v];
u = v;
}
}
2. 现代Dijkstra实现核心技术
2.1 邻接表存储优化
邻接表使用vector+pair的组合,完美适配稀疏图场景:
// 推荐邻接表声明方式
vector<vector<pair<int, int>>> adj(V); // adj[u] = { (v1, w1), (v2, w2)... }
// 添加边操作(有向图示例)
void addEdge(int u, int v, int w) {
adj[u].emplace_back(v, w);
}
2.2 优先队列加速
使用STL的priority_queue将查找最小距离节点的复杂度从O(V)降至O(logV):
// 最小堆定义(存储距离和节点)
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<>> pq;
// 初始化优先队列
pq.emplace(0, src);
dist[src] = 0;
注意:默认的priority_queue是最大堆,需通过
greater<>转换为最小堆
3. 完整工程级实现模板
3.1 核心算法框架
vector<int> dijkstra(int src, const vector<vector<pair<int, int>>>& adj) {
const int V = adj.size();
vector<int> dist(V, INT_MAX);
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<>> pq;
dist[src] = 0;
pq.emplace(0, src);
while (!pq.empty()) {
auto [d, u] = pq.top(); pq.pop();
if (d > dist[u]) continue; // 重要优化:跳过旧数据
for (const auto& [v, w] : adj[u]) {
if (dist[v] > dist[u] + w) {
dist[v] = dist[u] + w;
pq.emplace(dist[v], v);
}
}
}
return dist;
}
3.2 关键优化点解析
- 延迟删除技术 :通过
if(d > dist[u]) continue避免堆中数据重复处理 - 智能指针传递 :使用
const auto&避免不必要的拷贝 - 结构化绑定 :C++17的
auto [d, u]语法提升代码可读性
4. LeetCode 743「网络延迟时间」实战
4.1 问题重述
给定有向加权图和源节点,计算信号传播到所有节点的最短时间。如果不能使所有节点收到信号,返回-1。
4.2 完整AC代码
class Solution {
public:
int networkDelayTime(vector<vector<int>>& times, int n, int k) {
// 构建邻接表
vector<vector<pair<int, int>>> adj(n+1);
for(const auto& t : times) {
adj[t[0]].emplace_back(t[1], t[2]);
}
// Dijkstra算法
vector<int> dist(n+1, INT_MAX);
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<>> pq;
dist[k] = 0;
pq.emplace(0, k);
while(!pq.empty()) {
auto [d, u] = pq.top(); pq.pop();
if(d > dist[u]) continue;
for(const auto& [v, w] : adj[u]) {
if(dist[v] > dist[u] + w) {
dist[v] = dist[u] + w;
pq.emplace(dist[v], v);
}
}
}
// 计算结果
int max_time = *max_element(dist.begin()+1, dist.end());
return max_time == INT_MAX ? -1 : max_time;
}
};
4.3 复杂度分析
| 操作 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 构建邻接表 | O(E) | O(V+E) |
| 优先队列处理 | O(E logV) | O(V) |
| 结果计算 | O(V) | O(1) |
5. 常见陷阱与调试技巧
5.1 易错点排查表
| 错误现象 | 可能原因 | 解决方案 |
|---|---|---|
| 结果比预期大 | 未初始化dist为INF | 检查vector初始化值 |
| 部分节点距离错误 | 有向图/无向图处理错误 | 确认边的添加方式 |
| 堆溢出或超时 | 未处理重复节点 | 添加 if(d > dist[u]) continue |
| 返回-1但应可达 | 节点编号从0/1开始不一致 | 统一节点编号体系 |
5.2 调试建议
- 打印邻接表确认图构建正确
for(int u=1; u<=n; ++u) {
cout << u << ": ";
for(auto [v,w] : adj[u])
cout << "(" << v << "," << w << ") ";
cout << endl;
}
- 在优先队列操作后打印dist数组
- 使用小规模测试用例验证边界条件
更多推荐



所有评论(0)