因为std::priority_queue不支持动态更新节点权重,只能插入新副本导致重复出队,若不检查dist[u]是否过期(即pop时加if(d != dist[u]) continue),就会用过期距离松弛邻边而算错最短路径。

为什么直接用 std::priority_queue 会算错最短路径?
因为 std::priority_queue 不支持动态修改已入队元素的优先级。当某节点的更短距离被发现时,你无法更新它在堆中的位置,只能插入新副本——这导致同一节点多次出队,而旧的、过时的距离值可能仍留在队列里。若不做去重判断,就会用错误的距离松弛邻边,最终结果不可靠。
正确做法是:每次从堆中取出节点时,先检查其距离是否与当前已知最短距离一致。不一致就跳过。
- 维护一个
dist[]数组记录当前最短距离,初始化为INT_MAX - 每次
pop()后,加一句if (d != dist[u]) continue; - 只对未访问过的节点(或距离更优的节点)执行松弛操作
如何构造适合 Dijkstra 的最小堆比较逻辑?
std::priority_queue 默认是最大堆,必须显式指定比较器使其按距离升序排列。但注意:C++ 中 std::greater 对 pair 的行为容易误用——它先比第一项,再比第二项。若把 distance 放在 pair 第二位,排序就失效了。
推荐结构:pair<int int></int> 表示 (distance, node),并用 greater<pair>></pair> 或自定义 lambda:
priority_queue<pair int>, vector<pair int>>, greater<pair int>>> pq;</pair></pair></pair>
这样能保证距离小的排在前面。如果用 tuple 或自定义结构体,必须重载 operator 且语义为“小于即优先级更高”,否则逻辑反转。
邻接表存图时,vector<vector int>>></vector> 还是 vector<vector>></vector>?
两种都行,但前者更轻量、缓存友好,适合竞赛和快速实现;后者便于扩展(比如加边权类型、ID 标识)。关键点在于:Dijkstra 松弛时,你只需要知道“从 u 到 v 的边权 w”,不需要反向查边属性。
- 用
vector<vector int>>> graph</vector>:外层下标是起点 u,内层每个pair{v, w} - 避免用
map<int vector>></int>存图——常数大,且节点编号稀疏时浪费空间 - 若图含重边,建图时可取 min(w),或干脆不处理,让 Dijkstra 自然覆盖(因更短的边会先触发松弛)
初始化、边界和负权边为什么必须检查?
Dijkstra 本质依赖“已确定最短路的节点不会再被更短路径更新”这一性质,而这仅在所有边权 ≥ 0 时成立。一旦出现负权边,dist[v] 可能在出队后又被更小值更新,算法崩溃。
实操中常见疏漏:
- 没把起点
dist[src] = 0,其余全设为INT_MAX(不能用 -1 初始化) - 图不连通时,终点
dist[dst]保持INT_MAX,需单独判断是否可达 - 节点编号从 0 还是 1 开始?要和输入、图数组大小严格对应,越界访问会导致未定义行为
- 使用
INT_MAX作无穷大时,做dist[u] + w前必须判断dist[u] != INT_MAX,否则整数溢出
真正难的不是写完算法,而是确保每处下标、每轮比较、每次入堆都和当前 dist 状态同步——稍有脱节,错的不是结果,是你对中间状态的信任。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











