Bellman-Ford的队列优化
思想:每次仅对最短路程发生变化了的点的相邻边执行松弛操作。
如何知道当前哪些点的最短路程发生变化?此处可以用一个队列来维护这些点。
每次选取队首顶点u,对顶点u的所有出边进行松弛操作。例如有一条边u→v的边,如果通过u→v这条边使得源点到顶点v的最短路径变短(dis[u]+e[u][v]<dis[v]),且顶点v不在当前队列中,就将顶点v放入队尾。需要注意:同一个顶点同时在队列中出现多次是毫无意义的,所以我们需要一个数组来判重(判断哪些点已经在队列中)。在对顶点u的所有出边松弛完毕后,就将顶点u出队。接下来不断从队列中取出新的队首顶点再进行如上操作,直至队列空为止。
5 7
1 2 2
1 5 10
2 3 3
2 5 7
3 4 4
4 5 5
5 3 6
第一行2个整数n、m,n为顶点个数,m为边的个数。接下来m行,每行3个数x、y、z,表示顶点x到顶点y的边权值为z。
#include <stdio.h>
int main() {
int n,m,i,j,k;
int u[8],v[8],w[8]; //u、v、w数组大小要根据实际情况来设置,要比m的最大值大1
int first[6],next[6]; //first next数组大小要根据实际情况来设置,要比n的最大值大1
int dis[6]={0},book[6]={0}; //book数组用来记录哪些顶点已经在队列中
int que[101]={0},head=1,tail=1; //定义一个队列,并初始化队列
int inf=99999999;
scanf("%d %d",&n,&m);
//初始化dis数组,这里是1号顶点到其余各个顶点的初始路程
for(i=1;i<=n;i++)
dis[i]=inf;
dis[1]=0;
for(i=1;i<=n;i++) //初始化book数组,初始化为0,刚开始都不在队列中
book[i]=0;
for(i=1;i<=n;i++) //初始化first数组下标1~n的值为-1,表示1~n顶点暂时都没有边
first[i]=-1;
for(i=1;i<=m;i++) {
scanf("%d %d %d",&u[i],&v[i],&w[i]);//读入每一条边
next[i]=first[u[i]]; //关键语句 ,first[u[i]]保存顶点u[i]的第一条边的编号,next[i]存储“编号为i的边”的“下一条边”的编号
first[u[i]]=i;
}
//1号顶点入队
que[tail]=1;tail++;
book[1]=1; //标记1号顶点已经入队
while(head<tail) { //队列不为空的时候循环
k=first[que[head]]; //当前需要处理的队首顶点
while(k!=-1) { //扫描当前顶点所有的边
if(dis[v[k]] > dis[u[k]+wki]]) { //判断是否松弛成功
dis[v[k]] = dis[u[k]+w[k]]; //更新顶点1到顶点v[k]的路程
if(book[v[k]]==0) { //book数组用来判断顶点v[k]是否在队列中。如果不使用一个数组来标记的话,判 //断一个顶点是否在队列中,每次需要从队列的head到tail扫一遍,很浪费时间
que[tail]=v[k]; //入队
tail++;
book[v[k]]=1; //同时标记顶点v[k]已经入队
}
}
k=next[k];
}
book[que[head]]=0; //出队
head++;
}
for(i=1;i<=n;i++) //输出1号顶点到其余各个顶点的最短路径
printf("%d ",dis[i]);
getchar();getchar();
return 0;
}
总结:初始时将源点加入队列。每次从队首head取出一个顶点,并对与其相邻的所有顶点进行松弛尝试,若某个相邻的顶点松弛成功,且这个相邻的顶点不在队列中(不在head~tail之间),则将它加入到队列中。对当前顶点处理完毕后立即出队,并对下一个新队首进行如上操作,直到队列为空时算法结束。此处用了一个book来记录每个顶点是否处在队列中。也可以不要book数组,检查一个顶点是否在队列中,只需要把que[head]到que[tail]依次判断一遍就可以,但这样做的时间复杂度是O(N),而使用book数组来记录的话时间复杂度会降至O(1).
使用队列优化的Bellman-Ford算法的时间复杂度在最坏情况下也是O(NM)。通过队列优化的Bellman-Ford算法如何判断一个图是否有环?如果某个点进入队列的次数超过n次,那么这个图肯定存在负环。
用队列优化的Bellman-Ford算法的关键之处在于:只有那些在前一遍松弛中改变了最短路程估计值的顶点,才可能引起它们邻接点最短路程估计值发生改变。因此,用一个队列来存放被成功松弛的顶点,之后只对队列中的点进行处理,这就降低了算法的时间复杂度。
Floyd | Dijkstra | Bellman-Foed | 队列优化的Bellman-Ford | |
空间复杂度 | O(N*N) | O(M) | O(M) | O(M) |
时间复杂度 | O(N*N*N) | O((M+N)logN) | O(MN) | 最坏O(MN) |
适用情况 | 稠密图(和顶点关系密切) | 稠密图(和顶点关系密切) | 稀疏图(与边关系密切) | 稀疏图(与边关系密切) |
负权 | 可以解决 | 不能解决 | 可以解决 | 可以解决 |