Bellman-Ford
-
本质:DP,对边进行操作
-
特点:单源最短路,求解一个源点到其他所有点的最短距离
-
适用对象:小图,允许负权图和负环图(负环:图上边权之和为负的环)
-
存储结构:直接存边
-
核心思想:反复松弛所有边(最多进行 V − 1 V-1 V−1轮),若该边使距离更优则更新
-
算法流程:闫氏DP分析法
- 状态表示:
- 集合:定义源点 s s s到每个顶点的最短路长度 d i s dis dis(初始化为 + ∞ +\infty +∞表示该点不可达,源点初始化为0),路径数组 p a t h path path(存储其上一个来源顶点,初始化为 − 1 -1 −1表示无路径)
- 属性: M i n Min Min
- 状态计算:设当前边为
<
u
,
v
>
<u,v>
<u,v>,循环执行下列步骤,直到循环
V
−
1
V-1
V−1轮或没有结点
d
i
s
dis
dis再更新(若循环超过
V
−
1
V-1
V−1轮还在更新说明存在负环)
- 不选第 k k k条边:若选当前边不能使边的终点 v v v到源点 s s s的距离减小,则不选该边。 d i s [ v ] dis[v] dis[v]不变。
- 选第 k k k条边:若选当前边能够使边的终点 v v v到源点 s s s的距离减小,则选该边。 d i s [ v ] = d i s [ u ] + w dis[v]=dis[u]+w dis[v]=dis[u]+w。(该边被松弛)
- 状态转移方程式:
d
i
s
[
v
]
=
m
i
n
(
d
i
s
[
v
]
,
d
i
s
[
u
]
+
w
)
dis[v]=min(dis[v],dis[u]+w)
dis[v]=min(dis[v],dis[u]+w)
实际上,此处默认运用了滚动数组压到了一维。
- 状态表示:
-
复杂度: O ( V E ) O(VE) O(VE)
-
与Dijkstra算法的区别:
- Dijkstra每次考查的是先前未被考察且 d i s dis dis最小的点,一旦被考查后其将不再被考查,因此不能处理负权图
- Bellman-Ford每次都直接对所有边进行遍历,因此可适用于负权图和负环图
-
Bellman-Ford的优化:SPFA
实现
int n,m,s;//点数 边数 源点
struct edge{
int f,t,w;
}e[MAXE];
int dis[MAXV],path[MAXV];
void bellman(){
fill(begin(dis),end(dis),INF),fill(begin(path),end(path),-1);
dis[s]=0;
for(int k=0;k<n-1;k++)//进行V-1次循环
for(int i=0;i<m;i++)//遍历所有边
if(dis[e[i].f]!=INF&&e[i].w!=INF&&dis[e[i].t]>dis[e[i].f]+e[i].w){
dis[e[i].t]=dis[e[i].f]+e[i].w;
path[e[i].t]=e[i].f;
}
}
路径输出
void print(int s,int t){
if(s==t){cout<<s<<' ';return;}
print(s,path[t]);
cout<<t<<' ';
}
判断负环
在循环结束后,加入判断是否有边 < u , v > <u,v> <u,v>满足 d i s [ v ] > d i s [ u ] + w dis[v]>dis[u]+w dis[v]>dis[u]+w,若有则表明还可继续松弛,存在负环。
bool check(){
for(int i=0;i<m;i++)
if(dis[e[i].t]>dis[e[i].f]+e[i].w) //若是非连通图,需再加条件dis[e[i].t]!=INF
return 1;
return 0;
}
负环还可用SPFA进行判断。