「这是我参与11月更文挑战的第11天,活动详情查看:2021最后一次更文挑战」。
算法简介
(以下简称)算法是一种单源最短路径算法,他由和提出并因此得名,其中是动态规划的提出中。算法可以用于求特定点到任意点的最短路径,相比于算法,算法可以求带负权边的图,适用面更广。但是复杂度高,达到了。
假设采取邻接表存储,表示源点到号顶点的最短距离,存储的是第条边的起点、终点和权值。
核心代码
for(int i=1;i<n;i++){//循环n-1次
for(int j=1;j<=m;j++){//遍历所有的边
int u=from[j],v=to[j],w=val[j];
dist[v]=min(dist[v],dist[u]+w);//进行松弛操作
}
}
我们可以发现算法的松弛操作和迪杰斯特拉算法类似,但是算法用边做松弛,迪杰斯特拉算法用点做松弛。至于为什么是循环次,这是因为两个点之间的最短距离最多经过条边,超过这个数量还可以松弛就说明肯定有负环存在。所以算法不仅可以求解最短路径,还可以判断图是否有负环。
如果循环次之后,仍然可以松弛,那么说明存在负环。
for(int i=1;i<=m;i++){
int u=from[j],v=to[j],w=val[j];
if(dist[u]+w<dist[v]){
return false;//n-1次之后还能松弛说明有负环
}
}
return true;//否则返回true
优化
提前退出循环
在实际当中,普遍情况是并不需要条边就能得到最短路,所以次循环实际上可以提前退出,避免过大的循环次数。
优化点在于,如果某一轮没有成功松弛,那么就可以退出循环。
bool Ford(int x){
memset(dist,INF,sizeof(dist));//初始化x到任意点距离为无穷大
dist[x]=0;//x到自己距离为0
for(int i=1;i<n;i++){//循环n-1次
bool flag=true;
for(int j=1;j<=m;j++){//遍历所有的边
int u=from[j],v=to[j],w=val[j];
if(dist[u]+w<dist[v]){
dist[v]=min(dist[v],dist[u]+w);//进行松弛操作
flag=false;//能够松弛就不用结束
}
}
if(flag) return true;//不能松弛,提前结束
}
for(int i=1;i<=m;i++){
int u=from[i],v=to[i],w=val[i];
if(dist[u]+w<dist[v]){
return false;//n-1次之后还能松弛说明有负环
}
}
return true;//计算完成
}
队列优化—算法
- 算法有许多冗余松弛,我们可以看到算法需要遍历每一条边。实际上,只有那些路径变小了的点才可能使得与该点连通的点路径减小。
算法思想
我们需要维护一个队列,首先只存储起点,记数组存的距离,表示顶点是否被松弛过。
- 初始化,,,分别表示起点任意点距离无穷大,到自己距离为,所有点未被访问。
- 将起点编号入队,标记为表示已被访问。
- 从中取出队头顶点编号,记录未被访问。
- 松弛所有以为起点的边对应的终点。入队松弛成功且未被访问的顶点编号,并标记为已访问。
- 让的松弛次数加,如果超过顶点数则有负环,否则重复第步直到队列为空。
初始化
- 因为的初始化有点长,所以这里把初始化剥离出来了。
- 初始化到任意点的距离为,到起点距离,所有顶点未被访问,起点被访问,以及所有点松弛次。
void init(int x){
memset(dist,INF,sizeof(dist));//初始化x到任意点距离为无穷大
dist[x]=0;//x到自己距离为0
memset(visit,0,sizeof(visit));//初始化未访问
visit[x]=1;//初始化自己被访问
memset(in,0,sizeof(in));//初始化所有点未被松弛
SPFA(x);//调用SPFA
}
- 算法主要以记忆为主,不是特别容易理解,所以直接贴代码加注释来解释了。
bool SPFA(int x){
queue<int> Q;
Q.push(x);//入队x
while(!Q.empty()){
int i=Q.front();Q.pop();//出队
visit[i]=0;//标记为未访问
for(int j=head[i];j;j=last[j]){
int u=from[j],v=to[j],w=val[j];
if(dist[u]+w<dist[v]){
dist[v]=dist[u]+w;//进行松弛操作
if(!visit[v]){//如果未被访问
Q.push(v);//加入队列
visit[v]=1;//标记为已被访问
if(++in[v]>n) return false;//如果松弛超过n次说明有负环
}
}
}
}
return true;//计算完成
}
总结
本文所用算法都是类型,这样可以扩展到判定负环的问题上,的最坏复杂度和一样,都高达,所以实际上能用算法的一般还是采用实现,与更适用于带负权的图。