关于SPFA算法和一维优化

SPFA(Shortest Path Faster Algorithm)(队列优化)算法是求单源最短路径的一种算法,它还有一个重要的功能是判负环(在差分约束系统中会得以体现),在Bellman-ford算法的基础上加上一个队列优化,减少了冗余的松弛操作,是一种高效的最短路算法。


以上内容来自百度。。。


总而言之,SPFA算法是一种效率相对比较高的算法,对于数据很大的情况下也可以使用。

SPFA算法的时间复杂度是O(km),M为边数,K一般为2或3。

所以,SPFA的效率是非常高的。

例如有这样一个图:

初始值


第一步,我们从节点1开始,向节点2和节点3进行扩展




之后从节点2开始,向节点1和节点5进行扩展




因为节点3不能扩展,所以最终结果就是这样。


下面说说SPFA算法如何实现。

我们先把初始节点(设为1)加入一个队列里,之后设立一个F数组,F[i]表示从初始节点到第i个节点的最短路径长度。

明显F[1]为0,其它的随便设个比较大的数。

之后我们每次枚举队头的节点的每一条边,如果该边连向的节点的值大于(如果改为大于等于效率会降低)当前节点加上边长,就把连向的节点的值更新,同时把新的节点加入队列。

最后输出目标节点的值就可以了。


优化:我们可以把普通的队列改成循环队列,可以节省一定的空间,但是注意不要开得太小,否则答案可能会出错。


先来一段SPFA的代码(用邻接表储存边):

        h:=0;
        t:=1;

        d[1]:=1;

        fillchar(f,sizeof(f),10);

        f[1]:=0;

        while h<>t do
        begin
                inc(h);

                if h>10000 then
                h:=1;

                j:=d[h];

                for i:=1 to b[j] do
                begin
                        k:=a[j,i,1];
                        l:=a[j,i,2];

                        if f[k]>f[j]+l then
                        begin
                                inc(t);

                                if t>10000 then
                                t:=1;

                                d[t]:=k;

                                f[k]:=f[j]+l;
                        end;
                end;
        end;


但是这样用邻接表做,对于部分数据较大的题目空间会炸。。。

例如:http://172.16.0.132/junior/#main/show/2051

所以,我们要想办法优化空间。


如果要优化空间,最好的办法就是把二位数组降到近似一维。

就按照上面那题来说吧。

如果我们按照输入的边进行SPFA,数组就只要开到一千万,空间就刚好不会爆。

那我们是否可以根据输入的数据来计算,并对其进行优化?

答案是可以的。


我们先一步步来。

因为这是一个无向图,所以我们在输入的时候要复制一份方向相反的序列。

之后我们在扩展节点的时候,就可以把所有的边都枚举一遍。

如果发现当前边的出发点和当前节点相等,就说明这条边是属于这个节点的,就可以进行扩展节点。

但是这样时间肯定会爆。


继续优化。

如果我们先把边按照出发点进行一次快速排序,之后每次枚举时发现前一条边是属于这个点。

而当前边则属于另一个点时,我们就可以直接跳出循环。

但是还是会爆。


尽管时间会爆,但这并不能代表这个方法不能成立。

所以,我们可以用一个数组记录当前节点的第一条边的编号,之后每次循环时就只要从当前节点第一条边编号到下一节点第一条边编号-1就可以了。


附上上面那题的代码(一维SPFA+快排):

var
        a:array[1..20000000,1..3] of longint;
        b:array[-1..5000000] of longint;
        c:array[-1..5000001] of longint;
        f:array[0..5000000] of longint;
        d:array[1..10000000] of longint;
        n,m,s,i,x,y,z,h,t,j,k,l:longint;

procedure swap(var x,y:longint);
var
        z:longint;
begin
        z:=x;
        x:=y;
        y:=z;
end;

procedure qsort(l,r:longint);
var
        i,j,mid:longint;
begin
        i:=l;
        j:=r;
        mid:=a[(i+j) div 2,1];

        while i<=j do
        begin
                while a[i,1]<mid do inc(i);
                while a[j,1]>mid do dec(j);

                if i<=j then
                begin
                        swap(a[i,1],a[j,1]);
                        swap(a[i,2],a[j,2]);
                        swap(a[i,3],a[j,3]);

                        inc(i);
                        dec(j);
                end;
        end;

        if l<j then qsort(l,j);
        if i<r then qsort(i,r);
end;
begin
        assign(Input,'short.in'); reset(Input);
        assign(Output,'short.out'); rewrite(Output);

        readln(n,m,s);

        for i:=1 to m do
        begin
                readln(a[i,1],a[i,2],a[i,3]);

                a[i+m,1]:=a[i,2];
                a[i+m,2]:=a[i,1];
                a[i+m,3]:=a[i,3];

                inc(b[a[i,1]]);
                inc(b[a[i,2]]);
        end;

        b[-1]:=1;

        for i:=0 to n do
        c[i]:=c[i-1]+b[i-1];

        m:=m*2;

        c[n+1]:=m;

        qsort(1,m);

        h:=0;
        t:=1;

        d[1]:=1;

        fillchar(f,sizeof(f),10);

        f[1]:=0;

        while h<>t do
        begin
                inc(h);

                if h>10000000 then
                h:=1;

                j:=d[h];

                for i:=c[j] to c[j+1]-1 do
                begin
                        k:=a[i,2];
                        l:=a[i,3];

                        if f[k]>f[j]+l then
                        begin
                                inc(t);

                                if t>10000000 then
                                t:=1;

                                d[t]:=k;

                                f[k]:=f[j]+l;
                        end;
                end;
        end;

        writeln(f[s]);

        close(Input); close(Output);
end.

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值