Bellman-Ford算法在每一次实施松弛操作时,就会有一些顶点已经求得最短路径,此后这些顶点的最短路径的估计值就会一直保持不变,不再受后续松弛操作的影响,但是每次还要判断是否需要松弛,这里浪费了大量的时间.
SPFA(Shortest Path Faster Algorithm)是基于Bellman-Ford算法的改进,每次进队最短路径估计值发生变化了的顶点的所有出边执行松弛操作,借助一个队列.
思想:每次选取队首顶点u,对顶点u的所有出边进行松弛操作,例如有一条u->v的边,如果通过u->v的这条边使得源点到顶点v的最短路径变得更短,也即(dis[u] + e[u][v] < dis[v]),并且顶点v不在当前的队列(并用借助一个数组标记顶点是否已经在队列之中),则将顶点v放置队尾. 对顶点u的所有出边松弛完毕之后,就将顶点u出队,接下来不断从队列中取出新的队首顶点反复上面操作直到队列为空结束.
用队列优化的Bellman-Ford算法的关键之处:只有那些在前一遍松弛中改变了最短路径估计值的点,才可能引起它们邻接点最短路径估计值发生改变. 因此用一个队列保存被成成功松弛的顶点,之后只对队列中的点进行处理,这就降低了算法的时间复杂度.
Q:SPFA如何判断一个图是否有负环?
如果某个点进入队列次数超过n次,那么这个图肯定存在负环.
Code:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define INF 999999
int book[10]; /// 初始化为顶点都不在队列
int que[100]; /// 松弛成功并且顶点不在队列则并入队列
int main(int argc, char const *argv[])
{
int i, j, n, m;
int q1, q2, q3;
int dis[10], e[10][10];
int head, tail;
scanf("%d %d", &n, &m);
for(i = 1; i <= n; ++i)
{
for(j = 1; j <= n; ++j)
{
if(i == j)
{
e[i][j] = 0;
}
else
{
e[i][j] = INF;
}
}
}
for(i = 1; i <= m; ++i)
{
scanf("%d %d %d", &q1, &q2, &q3);
e[q1][q2] = q3;
}
for(i = 1; i <= n; ++i)
{
dis[i] = INF;
}
dis[1] = 0; /// 初始化dis[1]为0,其他为∞
head = tail = 1;
que[head] = 1; /// 1号顶点入队
book[1] = 1;
tail++;
while(head < tail)
{
for(i = 1; i <= n; ++i)
{
if(e[que[head]][i] != INF && dis[i] > dis[que[head]] + e[que[head]][i])
{
dis[i] = dis[que[head]] + e[que[head]][i];
if (!book[i]) /// 顶点不在队列,加入队列
{
book[i] = 1;
que[tail++] = i;
}
}
}
book[que[head]] = 0; /// 重新标记不在队列
head++; /// 相当于出队
}
for(i = 1; i <= n; ++i)
{
printf("%d ", dis[i]);
}
printf("\n");
system("pause");
return 0;
}