由于Bellman-Ford算法在每实施一次松弛操作后,就会有一些顶点已经求得其最短路,此后这些顶点的最短路的估计值就一直保持不变,不再受到后续松弛操作的影响,但是每次还要判断是否需要松弛,浪费了时间
优化方法:每次仅对最短路估计值发生改变了的顶点的所有出边进行松弛操作
题目描述
求图中1号顶点到2、3、4、5号顶点的最短路径
Input
5 7
1 2 2
1 5 10
2 3 3
2 5 7
3 4 4
4 5 5
5 3 6
第一行:顶点数、边数
a b c:从a点到b点的权重为c
Bellman-Ford优化算法思路:类似于Dijktra算法,只不过Bellman-Ford是对边进行松弛,而Dijktra算法是对顶点进行操作,不过都是采用广搜的做法,Bellman-Ford对队列队首元素的所有出边松弛,如果某个顶点已经入队,那么就标记就不用再入队了(相比于Dijktra算法是在所有顶点中寻找与顶点的连接,这种算法更快一点,但是如果是一个稠密图的话也就相差无几),直到队列中元素全部出队
代码如下
#include <stdio.h>
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
int n,m,i,j,k,dis[10],u[10],v[10],w[10],first[10],next[10],book[10];
void Bellman_Ford()
{
queue<int> q;
int now;
now = 1;
book[1] = 1;
q.push(now);
while(!q.empty())
{
now = q.front();
q.pop();
//注意要将出队顶点重新变为未标记,因为这个顶点的出边还有可能再次被松弛
book[now] = 0;
//取出与这个顶点now连接的第一边
k = first[now];
while(k != -1)
{
//记录与顶点now(u[k])连接到顶点v[k]
int Next = v[k];
if(dis[Next] > w[k] + dis[now])
{
dis[Next] = w[k] + dis[now]; //更新顶点1到顶点v[k]的路程
//如果顶点next没有入队
if(!book[Next])
{
book[Next] = 1;
q.push(Next);
}
}
//继续松弛与now相连的另一个出边
k = next[k];
}
}
}
int main()
{
scanf("%d %d",&n,&m);
//初始化dis数组
for(i = 1;i <= n;i++)
dis[i] = INT_MAX;
dis[1] = 0;
//初始化first数组下标1~n的值为-1,表示1~n的顶点暂时没有边
for(i = 1;i <= n;i++)
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]] = i;
}
Bellman_Ford();
for(i = 1;i <= n;i++)
printf("%d ",dis[i]);
return 0;
}
使用Bellman-Ford队列算法判断负权回路:如果某个点进入队列的次数超过n次,那么这个图定然存在负权回路
关键之处:只有那些在前一遍松弛中改变了最短路程的估计值的顶点,才能引起他们邻接点最短路程估计值的改变。因此,用一个队列来存放被成功松弛的顶点,之后只对队列中的点进行处理,这就降低了算法的时间复杂度