注意到Bellman-Ford算法实际上做了很多无用功。因为每次它都把所有的边枚举一次,而有一些边在上一次枚举中,起始端顶点都没有更新,那么在这一次枚举中,肯定不会进行松弛。那么我们可以使用一个队列来进行活跃点的维护。我们定义在这个队列里的每个元素x都必须满足:d[x]被更新,但是未利用d[x]去更新其它的点。那么初始时把起始点1放进队列里面,然后对它进行松弛操作,并把d值被更新的点放入队列之中,最后把当前这个做完松弛操作的点出队。重复这个操作直到队列中没有元素,那么所有点的d值都被确定了。
这个算法同样可用于有负权边的情况之中。此外,该算法也可以用来判断负权环,具体方法就是如果某一个点入队超过n次,那么判断有负权环。
时间复杂度O(kE),其中k为一个小常数,大致在[3,5]这个范围内。代码实现如下:
procedure spfa;
var
head,tail,t:longint;
begin
head:=0; tail:=1;
list[1]:=1;
v[1]:=1; //初始时把起始点先放进队列里
while head<>tail do
begin
head:=head+1;
t:=ls[list[head]];
while t>0 do
with g[t] do
begin
if d[x]+w<d[y] then //松弛操作
begin
d[y]:=d[x]+w;
if v[y]=0 then //新的点满足未松弛但被更新了d值
begin
v[y]:=1;
tail:=tail+1;
list[tail]:=y;
end;
end;
t:=next;
end;
v[list[head]]:=0;
end;
end;
练习题:洛谷 1144、3371(模板题) //前两题必做,后面选做
洛谷2296 洛谷1073