每次选取队首顶点u,对顶点u的所有出边进行松弛操作
例如有一条u→v的边,如果通过u→v这条边使得源点到顶点v的最短路径变短,且顶点v不在当前的队列中,就将顶点v放入队尾。
同一个顶点同时在队列中出现多次是毫无意义的,因此需要一个数组来判重
在对顶点u的所有边松弛完毕后,酒店顶点v出队
接下来不断从队列中取出新的队首顶点再进行如上操作,直到队列空为止
初始时将源点加入队列。
每次从队首取出一个点,并对与其相邻的所有顶点进行松弛尝试,若某个相邻的顶点松弛成功,且这个相邻的顶点不在队列中,则将它加入到队列。
对当前顶点处理完毕后立即出队,并对下一个新队首进行如上操作,直到队列为空时,算法结束
这个Bellman-Ford算法与BFS有些相似,但是与BFS不同的是,一个顶点很可能出队后又再次被放入队列
即:
当一个顶点的最短路径估计值变小之后,需要对其所有出边进行松弛,但是如果这个顶点的最短路径估计值再次变小,仍需要对其所有出边进行再次松弛,这样才能保证相邻顶点的最短路程值同步更新
最坏时间复杂度:
O(NM)
通过队列优化的Bellman-Ford算法如何判断一个图是否具有负环?
如果某个点进入队列的次数超过了n次,那么这个图肯定存在负环
用队列优化的Bellman-Ford算法的关键之处:
只有在前一遍松弛中改变了最短路程估计值的顶点,才可能引起他们邻接点最短路程估计值发生改变。
因此用一个队列来存放那些被成功松弛的顶点,之后只对队列中的点进行处理
完整代码
#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[8];
//first要比n的最大值大1,next要比m的最大值大1
int dis[6] = { 0 }; //源点到各个点的最短路径
int book[6] = { 0 }; //book用于记录哪些顶点存放在queue中
int que[101] = { 0 }; //定义一个队列
int head = 1, tail = 1; //初始化队首和队尾
int inf = 9999999; //定义无穷
scanf("%d%d", &n, &m);
for (i = 1; i <= n; i++) //初始化dis的值
{
dis[i] = inf;
}
dis[1] = 0; //把源点到自身设置为0
for (i = 1; i <= n; i++) //初始化book,表示点均不在队列中
book[i] = 0;
for (i = 1; i <= n; i++) //初始话first,表示各个点都没有边
first[i] = -1;
for (i = 1; i <= m; i++)
{
scanf("%d%d", &u[i], &v[i], &w[i]);
//输入各个边的始点,终点,权值
next[i] = first[u[i]]; //建立邻接表核心语句
first[u[i]] = i;
}
que[tail] = 1; //将源点入队
tail++;
book[1] = 1; //标记源点已在队列中
while (head < tail)
{
k = first[que[head]]; //取得当前要处理的队首顶点
while (k != -1)
{
if (dis[v[k]] > dis[u[k]] + w[k]) //Bellman-Ford核心语句
dis[v[k]] = dis[u[k]] + w[k];
if (book[v[k]] == 0) //book用于标记v[k]这个点是否在队列中
{
que[tail++] = v[k];
book[v[k]] = 1;
}
k = next[k];
}
//出队
book[que[head]] = 0;
head++;
}
//下面就是输出1号到各个顶点的最短路径
for (i = 1; i <= n; i++)
{
printf("%d ", dis[i]);
}
return 0;
}