最短路径问题是图论中的经典问题,核心是在加权图中找到两个顶点之间总权重最小的路径。这里的"权重"可以代表实际场景中的距离、时间、成本等各种度量指标。Dijkstra算法特别适合解决边权非负的图结构,这是因为它基于贪心策略的特性决定的。
注意:当图中存在负权边时,Dijkstra算法可能得到错误结果,此时应考虑Bellman-Ford或SPFA算法。
让我们用一个简单的城市交通网络来理解算法流程:
这个"选择当前最近节点→更新邻居距离"的过程,正是贪心策略的体现——每次局部最优选择最终导向全局最优解。
cpp复制struct node {
int x, dis;
friend bool operator < (node n1, node n2){
return n1.dis > n2.dis; // 小根堆关键实现
}
};
priority_queue<node> pq;
这个结构体设计有两个精妙之处:
cpp复制void dijkstra(){
memset(dis, 0x3f, sizeof(dis)); // 初始化为极大值
dis[s] = 0;
pq.push({s, 0});
while (!pq.empty()){
int u = pq.top().x; pq.pop();
if (vis[u]) continue; // 已确定最短路径的节点跳过
vis[u] = true;
for (auto [y, z] : g[u]) { // C++17结构化绑定
if (dis[y] > dis[u] + z){ // 松弛操作
dis[y] = dis[u] + z;
pq.push({y, dis[y]});
}
}
}
}
防溢出处理:
cpp复制#define int long long
在竞赛题目中,边权累加可能导致int溢出,这是常见坑点
无穷大表示:
cpp复制0x3f3f3f3f3f3f3f3f // long long下的INF
(1 << 31) - 1 // 题目要求的特定输出
使用十六进制初始化可以保证memset正确设置极大值
邻接表存储:
cpp复制vector<pair<int, int>> g[N]; // first是目标节点,second是边权
比邻接矩阵更节省空间,适合稀疏图
| 图类型 | 处理方式 | 示例代码 |
|---|---|---|
| 有向图 | 直接添加边 | g[u].push_back({v, w}); |
| 无向图 | 添加双向边 | g[u].push_back({v, w});g[v].push_back({u, w}); |
| 多权图 | 使用结构体存储多个权重 | struct Edge {int to, w1, w2}; |
| 版本 | 时间复杂度 | 空间复杂度 | 适用场景 |
|---|---|---|---|
| 原始版 | O(n²) | O(n) | 稠密图,节点少 |
| 堆优化版 | O(mlogn) | O(n+m) | 稀疏图,边数较少 |
| 斐波那契堆版 | O(m+nlogn) | O(n) | 理论最优,实现复杂 |
次短路问题:
维护两个距离数组,同时更新最短路和次短路
k短路问题:
使用A*算法结合Dijkstra
分层图最短路:
构建多层图处理特殊边权情况
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 结果比预期大 | int溢出 | 改用long long |
| 部分节点距离不正确 | 未处理重边 | 取重边中最小权值 |
| 程序运行超时 | 未使用堆优化 | 改用优先队列实现 |
| 输出随机值 | 未初始化数组 | 检查memset或循环初始化 |
| 死循环 | 自环边处理不当 | 添加自环检测逻辑 |
输入输出加速:
cpp复制ios::sync_with_stdio(false);
cin.tie(0);
vector预分配:
cpp复制g.resize(n+1); // 避免动态扩容开销
C++17优化:
cpp复制for (auto [y, z] : g[u]) // 结构化绑定比.first/.second更快
堆实现选择:
Bellman-Ford:
SPFA:
Floyd:
在算法竞赛中,我习惯在实现时添加DEBUG输出,关键节点打印距离数组,这能快速定位逻辑错误。另一个实用技巧是使用宏定义简化代码:
cpp复制#define rep(i, a, b) for(int i = (a); i <= (b); i++)
#define per(i, a, b) for(int i = (a); i >= (b); i--)
对于无向图的处理,容易忘记添加双向边,我通常会写一个add_edge函数统一处理:
cpp复制void add_edge(int u, int v, int w) {
g[u].emplace_back(v, w);
g[v].emplace_back(u, w); // 无向图自动处理双向边
}
最后要提醒的是,虽然Dijkstra模板看似简单,但在实际比赛中,约30%的错误源于未考虑图的具体特性(如重边、自环、不连通等情况)。建议每次实现后,用这几个测试用例验证: