Bellman-ford算法
要说求最短路径可以用之前学的Dijkstra,但是Dijkstra 不能处理负权边,更不能处理负环;Bellman-Ford 可以!
在图论体系中:
- Dijkstra:贪心、快、只能正权
- Bellman-Ford:迭代松弛、稳、支持负权、可判负环(高级图论必备)
松弛操作
给定一条边:u → v,边权 w
松弛公式
if(dist[v] > dist[u] + w)
dist[v]=dist[u]+w
说明:走 u 中转到 v,比原来 v 的路径更短,更新最短路。
发现一条更近的路,就更新距离。
Bellman-Ford 不挑点、不挑边,暴力对所有边反复松弛。
判断负环
存在一个环:绕一圈总边权和为负数。
可以无限绕环,路径长度无限变小 → 没有最短路!
设图一共有 n 个点。
在无负环的图中:
任意两点的最短路径,最多经过 n-1 条边
比如 3 个点:最短路径最多 2 条边。
所以:
- 第 1 轮松弛:更新所有「1条边」的最短路
- 第 2 轮松弛:更新所有「2条边」的最短路
- ......
- 第 n-1 轮松弛:更新所有最长合法最短路
n-1 轮之后,所有点的最短路一定已经收敛,不会再更新 ,如果第 n 轮还能成功松弛,就能判断有负环
算法流程
- 初始化:所有点距离无穷大,起点距离为 0
- 迭代 n-1 轮 :每一轮遍历 所有 m 条边 做松弛
- 第 n 轮校验:还能松弛 = 存在负权环
时间复杂度:O(n*m)
适合:点数小、需要处理负权、判负环、差分约束问题。
Dijkstra、Bellman-ford与SPFA 的对比
整个最短路体系层级:
- Bellman-Ford:暴力松弛、最稳、可负权、可负环、最慢
- SPFA:队列优化版 Bellman-Ford
- Dijkstra:贪心堆优化、只能正权、最快
Bellman-Ford 是理论基石,SPFA 是进行了队列优化。
Dijkstra 需要「遍历某个点的所有出边」,必须用邻接表/二维数组;Bellman-Ford 不需要定点遍历,它每一轮直接遍历「整张图的所有边」。
判断负环模板
三个一维数组:u[](起点)、v[](终点)、w[](边权),
#include<bits/stdc++.h>
using namespace std;
const int N=6005;
const int INF=0x3f3f3f3f;
int u[N],v[N],w[N],dist[N];
int n,m,cnt;
bool bellman(){
for(int i=1;i<=n;i++){
dist[i]=INF;
}
dist[1]=0;
for(int i=1;i<n;i++){
bool ok=false;
for(int j=1;j<=cnt;j++){
if(dist[u[j]]!=INF&&dist[v[j]]>dist[u[j]]+w[j]){
dist[v[j]]=dist[u[j]]+w[j];
ok=true;
}
}
if(!ok){
break;
}
}
for(int j=1;j<=cnt;j++){
if(dist[u[j]]!=INF&&dist[v[j]]>dist[u[j]]+w[j]){
return true;
}
}
return false;
}
int main(){
int t;
cin>>t;
while(t--){
cnt=0;
cin>>n>>m;
for(int i=1;i<=m;i++){
int a,b,c;
cin>>a>>b>>c;
if(c>=0){
cnt++;
u[cnt]=a;
v[cnt]=b;
w[cnt]=c;
cnt++;
u[cnt]=b;
v[cnt]=a;
w[cnt]=c;
}
else{
cnt++;
u[cnt]=a;
v[cnt]=b;
w[cnt]=c;
}
}
if(bellman()){
cout<<"YES"<<endl;
}
else{
cout<<"NO"<<endl;
}
}
return 0;
}