Day5
这段代码是 SPFA 算法(Shortest Path Faster Algorithm),而且是一个专门用于判断负环的 SPFA 版本。
我们可以通过以下三个“铁证”断定它不是 Dijkstra:
vis数组的逻辑: 在 Dijkstra 中,vis意味着“最短路已确定,不再更改”;而这里的vis是配合q.push使用的,意味着“该点是否已在队列中”。++cnt[v] >= n: 这是判定负环的杀手锏,Dijkstra 无法判定负环。- 队列类型: 虽然代码中没写
q的定义,但配合!vis[v]的入队逻辑,这个q一定是一个普通的std::queue。
核心代码逐行拆解
1. 遍历邻居
for(int i=head[u]; i!=-1; i=e[i].nxt)
{
v = e[i].to, w = e[i].val;
- 这部分通过链式前向星找到从点
u连出的所有邻居v和边权w。 - 注意:这里你的
head初始化是-1,所以循环条件是i != -1。
2. 松弛操作 (Relaxation)
if(dis[v] > dis[u] + w)
{
dis[v] = dis[u] + w;
- 这是所有最短路算法的核心:如果通过
u走到v发现比原来的路径更短,就更新dis[v]。
3. 入队与判重 (The Queue Logic)
if(!vis[v]) // 如果点 v 不在当前的队列里
{
vis[v] = true; // 标记它已经进队了
...
q.push(v); // 把它丢进队列
}
- 关键区别: 在 SPFA 中,如果一个点的距离变短了,它就有可能去更新它的邻居。但如果它已经在队列里等待处理了,我们就没必要再把它放进去一次。
- 这就是
if(!vis[v])的作用:防止队列中出现大量重复的点,优化性能。
4. 负环判定 (Negative Cycle Detection)
if(++cnt[v] >= n)
{
printf("YES\n"); // 发现负环!
return;
}
- 原理: 在一个含有 \(n\) 个点的图中,任意两点间的最短路径最多只包含 \(n-1\) 条边。
- 如果点
v的入队次数(或者说被更新次数)达到了 \(n\) 次,说明它被卷入了一个权值为负的环。在这个环里,路越走越短,永远没有尽头。 - 一旦满足这个条件,算法立即终止并报告“有负环”。
总结:这段代码的运行流程
- 从队列取出点
u。 - 遍历
u的所有邻居v。 - 如果
v变短了:- 更新
v的距离。 - 检查
v是否已经在队列里。 - 如果不在队列:
v的入队次数+1。- 如果入队次数 \(\ge n\):报告“有负环”并退出。
- 把
v标记为在队列中,并压入队列。
- 更新
- 点
u处理完,出队,并将vis[u]设为false(表示以后u如果再变短,还能再进队)。
为什么 Dijkstra 不能这样写?
Dijkstra 每次选的是“当前全局最短”的点,它默认每个点处理一次就结束了。而 SPFA 允许点反复入队,正是这种反复入队的特性,让它能够检测出那些由于负环而导致距离不断缩小的异常情况。
2. dist[n] == INF 这句话的意义是什么?
在算法开始前,我们将所有点到起点的距离都设为 INF(无穷大)。这代表:“在还没开始找路之前,我认为所有点都是不可达的。”
- 如果存在路径:Dijkstra 算法会不断“松弛”边。只要有一条路能通向
n,dist[n]就会被更新成一个具体的、比INF小的数值(比如 10 或 100)。 - 如果不存在路径:如果起点和终点根本不连通(比如图是断开的),那么
dist[n]就永远不会被更新,它会一直保持初始值INF。
所以,if (dist[n] == INF) 是在做一个判定:
- 成立:说明从 1 号点出发,无论如何也走不到 \(n\) 号点。按照题目要求,输出
-1。 - 不成立:说明
dist[n]已经变成了一个具体的距离,路径是存在的,可以放心去重建并输出。
总结
dist数组告诉你:能不能到,以及最短多远。pre数组告诉你:具体怎么走。

浙公网安备 33010602011771号