Bellman-Ford算法如果存在一个顶点已经达到最短路径但还会继续判断是否需要松弛
优化关键:每次仅对最短路径估计值发生变化了的顶点的所有出边执行松弛操作
队列优化的Bellman-Ford算法
原理:我们可以用一个队列来维护,该顶点最短路径估计值发生变化的点,因为如果该顶点最短路径发送了变化,则对后续经过该顶点的最短路径也会发生变化,则需要再次以该顶点进行松弛
算法关键
使用邻接表来存储边和点(稀疏图)
先将源点入队,然后以该源点为队首进行相邻边的松弛,如果该队首顶点的出边成功松弛,则出边对应的顶点入队
同时,入队的点必须不重复,因为重复则会进行无意义的松弛,松弛不会成功
该队首所有邻边松弛完毕后,出队,队列中下一个顶点为队首进行松弛,以此类推
代码
#include <iostream>
using namespace std;
int main()
{
int n,m;
cin>>n>>m;
//存储边的信息
int u[m+1],v[m+1],w[m+1];
//存储点和其所有相邻边
int first[n+1],next[m+1];
int dis[n+1]={0},book[n+1]={0};//book数组记录哪些顶点已经在队列中
int que[101]={0},head = 1,tail = 1;//定义队列,并初始化
int inf = 99999999;
//初始化dis数组,以1作为源点为例
for(int i=1;i<=n;i++){
dis[i] = inf;
}
dis[1] = 0;
//初始化first数组,即邻接表存储所有顶点的相邻边
for(int i=1;i<=n;i++){
first[i] = -1;
}
//建立邻接表
for(int i=1;i<=m;i++){
cin>>u[i]>>v[i]>>w[i];
//建立邻接表的核心算法
next[i] = first[u[i]];
first[u[i]] = i;
}
//1号顶点入队
que[tail] = 1;
tail ++;
book[1] = 1;//标记1号顶点已经在队列中
while(head < tail){//队列不为空循环
int k = first[que[head]];//取出需要处理的顶点
while(k != -1){
if(dis[v[k]] > dis[u[k]] + w[k]){
dis[v[k]] = dis[u[k]] + w[k];//更新顶点1到v[k]的最短路径
//判断该顶点是否在队列中
if(book[v[k]] == 0){
//入队
que[tail] = v[k];
tail ++;
book[v[k]] = 1;
}
}
k = next[k];
}
//出队
book[que[head]] = 0;
head ++;
}
//输出1到其余顶点的最短路径
for(int i=1;i<=n;i++){
cout<<dis[i]<<" ";
}
return 0;
}
通过队列优化的Bellman-Ford算法判断图是否有负权环:如果某个点进入队列的次数超过n此,那么一定存在负权环
复杂度分析:
时间复杂度:最坏情况下也是O(NM)
空间复杂度:O(M)