spfa算法+前向星优化.docx

上传人:b****2 文档编号:272327 上传时间:2023-04-28 格式:DOCX 页数:14 大小:37.69KB
下载 相关 举报
spfa算法+前向星优化.docx_第1页
第1页 / 共14页
spfa算法+前向星优化.docx_第2页
第2页 / 共14页
spfa算法+前向星优化.docx_第3页
第3页 / 共14页
spfa算法+前向星优化.docx_第4页
第4页 / 共14页
spfa算法+前向星优化.docx_第5页
第5页 / 共14页
spfa算法+前向星优化.docx_第6页
第6页 / 共14页
spfa算法+前向星优化.docx_第7页
第7页 / 共14页
spfa算法+前向星优化.docx_第8页
第8页 / 共14页
spfa算法+前向星优化.docx_第9页
第9页 / 共14页
spfa算法+前向星优化.docx_第10页
第10页 / 共14页
spfa算法+前向星优化.docx_第11页
第11页 / 共14页
spfa算法+前向星优化.docx_第12页
第12页 / 共14页
spfa算法+前向星优化.docx_第13页
第13页 / 共14页
spfa算法+前向星优化.docx_第14页
第14页 / 共14页
亲,该文档总共14页,全部预览完了,如果喜欢就下载吧!
下载资源
资源描述

spfa算法+前向星优化.docx

《spfa算法+前向星优化.docx》由会员分享,可在线阅读,更多相关《spfa算法+前向星优化.docx(14页珍藏版)》请在冰点文库上搜索。

spfa算法+前向星优化.docx

spfa算法+前向星优化

Spfa+前向星优化

SPFA算法

  求单源最短路的SPFA算法的全称是:

ShortestPathFasterAlgorithm。

  SPFA算法是西南交通大学段凡丁于1994年发表的.

  从名字我们就可以看出,这种算法在效率上一定有过人之处。

  很多时候,给定的图存在负权边,这时类似Dijkstra等算法便没有了用武之地,而Bellman-Ford算法的复杂度又过高,SPFA算法便派上用场了。

  简洁起见,我们约定有向加权图G不存在负权回路,即最短路径一定存在。

当然,我们可以在执行该算法前做一次拓扑排序,以判断是否存在负权回路,但这不是我们讨论的重点。

  我们用数组d记录每个结点的最短路径估计值,而且用邻接表来存储图G。

我们采取的方法是动态逼近法:

设立一个先进先出的队列用来保存待优化的结点,优化时每次取出队首结点u,并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛操作,如果v点的最短路径估计值有所调整,且v点不在当前的队列中,就将v点放入队尾。

这样不断从队列中取出结点来进行松弛操作,直至队列空为止。

  定理:

只要最短路径存在,上述SPFA算法必定能求出最小值。

  证明:

每次将点放入队尾,都是经过松弛操作达到的。

(松弛操作的原理是著名的定理:

“三角形两边之和大于第三边”,在信息学中我们叫它三角不等式。

所谓对i,j进行松弛,就是判定是否d[j]>d[i]+w[i,j],如果该式成立则将d[j]减小到d[i]+w[i,j],否则不动。

)换言之,每次的优化将会有某个点v的最短路径估计值d[v]变小。

所以算法的执行会使d越来越小。

由于我们假定图中不存在负权回路,所以每个结点都有最短路径值。

因此,算法不会无限执行下去,随着d值的逐渐变小,直到到达最短路径值时,算法结束,这时的最短路径估计值就是对应结点的最短路径值。

(证毕)

  期望的时间复杂度O(ke),其中k为所有顶点进队的平均次数,可以证明k一般小于等于2。

  实现方法:

建立一个队列,初始时队列里只有起始点,在建立一个表格记录起始点到所有点的最短路径(该表格的初始值要赋为极大值,该点到他本身的路径赋为0)。

然后执行松弛操作,用队列里有的点去刷新起始点到所有点的最短路,如果刷新成功且被刷新点不在队列中则把该点加入到队列最后。

重复执行直到队列为空

判断有无负环:

如果某个点进入队列的次数超过N次则存在负环(SPFA无法处理带负环的图)

在一幅图中,我们仅仅知道结点A到结点E的最短路径长度是73,有时候意义不大。

这附图如果是地图的模型的话,在算出最短路径长度后,我们总要说明“怎么走”才算真正解决了问题。

如何在计算过程中记录下来最短路径是怎么走的,并在最后将它输出呢?

Path[]数组,Path[i]表示从S到i的最短路径中,结点i之前的结点的编号。

注意,是“之前”,不是“之后”。

最短路径算法的核心思想成为“松弛”,原理是三角形不等式,方法是上文已经提及的。

我们只需要在借助结点u对结点v进行松弛的同时,标记下Path[v]=u,记录的工作就完成了。

 

SPFA算法采用图的存储结构是邻接表,方法是动态优化逼近法。

算法中设立了一个先进先出的队列Queue用来保存待优化的顶点,优化时从此队列里顺序取出一个点w,并且用w点的当前路径D[W]去优化调整其它各点的路径值D[j],若有调整,即D[j]的值改小了,就将J点放入Queue队列以待继续进一步优化。

反复从Queue队列里取出点来对当前最短路径进行优化,直至队空不需要再优化为止,此时D数组里就保存了从源点到各点的最短路径值。

下面举一个实例来说明SFFA算法是怎样进行的:

设有一个有向图G={V,E},其中,V={V0,V1,V2,V3,V4},E={,,,,,,}={2,10,3,7,4,5,6},见下图:

算法执行时各步的Queue队的值和D数组的值由下表所示。

表一实例图SPFA算法执行的步骤及结果

初始

第一步

第二步

第三步

第四步

第五步

queue

D

queue

D

queue

D

queue

D

queue

D

queue

D

V0

0

V1

0

V4

0

V2

0

V3

0

0

V4

2

V2

2

2

2

2

5

5

5

5

9

9

10

9

9

9

9

算法执行到第五步后,队Queue空,算法结束。

源点V0到V1的最短路径为2,到V2的最短路径为5,到V3的最短路径为9,到V4的最短路径为9,结果显然是正确的。

SPFA在形式上和宽度优先搜索非常类似,不同的是宽度优先搜索中一个点出了队列就不可能重新进入队列,但是SPFA中一个点可能在出队列之后再次被放入队列,也就是一个点改进过其它的点之后,过了一段时间可能本身被改进,于是再次用来改进其它的点,这样反复迭代下去。

 标准SPFA过程

(以求某个结点t到某个结点s的最短路为例,稍加修改即为单源最短路)

Pascal语言代码

  const

  maxp=10000;{最大结点数}

  var{变量定义}

  p,c,s,t:

longint;{p,结点数;c,边数;s:

起点;t:

终点}

  a,b:

array[1..maxp,0..maxp]oflongint;{a[x,y]存x,y之间边的权;b[x,c]存与x相连的第c个边的另一个结点y}

  d:

array[1..maxp]ofinteger;{队列}

  v:

array[1..maxp]ofboolean;{是否入队的标记}

  dist:

array[1..maxp]oflongint;{到起点的最短路}

  head,tail:

longint;{队首/队尾指针}

  procedureinit;

  vari,x,y,z:

longint;

  begin

  read(p,c);

  fori:

=1tocdo

  begin

  readln(x,y,z);{x,y:

一条边的两个结点;z:

这条边的权值}

  inc(b[x,0]);b[x,b[x,0]]:

=y;a[x,y]:

=z;{b[x,0]:

以x为一个结点的边的条数}

  inc(b[y,0]);b[y,b[y,0]]:

=x;a[y,x]:

=z;

  end;

  readln(s,t);{读入起点与终点}

  end;

  procedurespfa(s:

longint);{SPFA}

  vari,j,now,sum:

longint;

  begin

  fillchar(d,sizeof(d),0);

  fillchar(v,sizeof(v),false);

  forj:

=1topdodist[j]:

=maxlongint;

  dist[s]:

=0;v[s]:

=true;d[1]:

=s;{队列的初始状态,s为起点}

  head:

=1;tail:

=1;

  whilehead<=taildo{队列不空}

  begin

  now:

=d[head];{取队首元素}

  fori:

=1tob[now,0]do

  ifdist[b[now,i]]>dist[now]+a[now,b[now,i]]then

  begin

  dist[b[now,i]]:

=dist[now]+a[now,b[now,i]];{修改最短路}

  ifnotv[b[now,i]]then{扩展结点入队}

  begin

  inc(tail);

  d[tail]:

=b[now,i];

  v[b[now,i]]:

=true;

  end;

  end;

  v[now]:

=false;{释放结点,一定要释放掉,因为这节点有可能下次用来松弛其它节点}

  inc(head);{出队}

  end;

  end;

  procedureprint;

  begin

  writeln(dist[t]);

  end;

  begin

  init;

  spfa(s);

  print;

  end.

前向星优化

星形(star)表示法的思想与邻接表表示法的思想有一定的相似之处。

对每个节点,它也是记录从该节点出发的所有弧,但它不是采用单向链表而是采用一个单一的数组表示。

也就是说,在该数组中首先存放从节点1出发的所有弧,然后接着存放从节点2出发的所有孤,依此类推,最后存放从节点

出发的所有孤。

对每条弧,要依次存放其起点、终点、权的数值等有关信息。

这实际上相当于对所有弧给出了一个顺序和编号,只是从同一节点出发的弧的顺序可以任意排列。

此外,为了能够快速检索从每个节点出发的所有弧,我们一般还用一个数组记录每个节点出发的弧的起始地址(即弧的编号)。

在这种表示法中,可以快速检索从每个节点出发的所有弧,这种星形表示法称为前向星形(forwardstar)表示法。

例如,在例7所示的图中,仍然假设弧(1,2),(l,3),(2,4),(3,2),(4,3),(4,5),(5,3)和(5,4)上的权分别为8,9,6,4,0,3,6和7。

此时该网络图可以用前向星形表示法表示如下:

节点对应的出弧的起始地址编号数组(记为

节点号

1

2

3

4

5

6

起始地址

1

3

4

6

7

9

记录弧信息的数组

弧编号

1

2

3

4

5

6

7

8

起点

1

1

2

3

4

4

5

5

终点

2

3

4

2

3

5

3

4

8

9

6

4

0

3

6

7

在数组

中,其元素个数比图的节点数多1(即

),且一定有

对于节点

,其对应的出弧存放在弧信息数组的位置区间为

如果

,则节点

没有出弧。

这种表示法与弧表表示法也非常相似,“记录弧信息的数组”实际上相当于有序存放的“弧表”。

只是在前向星形表示法中,弧被编号后有序存放,并增加一个数组(

)记录每个节点出发的弧的起始编号。

fori:

=1tomdo

 readln(a[i],b[i],e[i]);

qsort(1,m);

fori:

=1tomdo

 iff[a[i]]=0thenf[a[i]]:

=i;

f[n+1]:

=m+1;

fori:

=ndownto1do

 iff[i]=0thenf[i]:

=f[i+1];

通常用在点的数目太多,或两点之间有多条弧的时候。

一般在别的数据结构不能使用的时候才考虑用前向星。

除了不能直接用起点终点定位以外,前向星几乎是完美的。

前向星最常用的是来优化spfa

最基本的前项性优化的spfa(有向图)

var

  a,b,e:

array[1..1000]oflongint;

  vis:

array[1..2000]ofboolean;

  q,d,f:

array[1..2001]oflongint;

  n,m,i,s,t:

longint;

  procedureqsort(l,r:

longint);

  vari,j,x,y:

longint;

  begin

  i:

=l;

  j:

=r;

  x:

=a[(l+r)shr1];

  repeat

  whilea[i]

  whilea[j]>xdodec(j);

  ifnot(i>j)thenbegin

  y:

=a[i];a[i]:

=a[j];a[j]:

=y;

  y:

=b[i];b[i]:

=b[j];b[j]:

=y;

  y:

=e[i];e[i]:

=e[j];e[j]:

=y;

  inc(i);

  dec(j);

  end;

  untili>j;

  ifi

  ifl

  end;

  procedurespfa(s:

longint);

  vari,k,l,t:

longint;

  begin

  fillchar(vis,sizeof(vis),0);

  fori:

=1tondod[i]:

=maxlongint;

  d[s]:

=0;

  l:

=0;

  t:

=1;

  q[1]:

=s;

  vis[s]:

=true;

  repeat

  l:

=lmod10000+1;

  k:

=q[l];

  fori:

=f[k]tof[k+1]-1do

  ifd[k]+e[i]

  begin

  d[b[i]]:

=d[k]+e[i];

  ifnotvis[b[i]]thenbegin

  t:

=tmod10000+1;

  q[t]:

=b[i];

  vis[b[i]]:

=true;

  end;

  end;

  vis[k]:

=false;

  untill=t;

  end;

  Begin

  readln(n,m);

  fori:

=1tomdo

  readln(a[i],b[i],e[i]);

  qsort(1,m);

  fori:

=1tomdo

  iff[a[i]]=0thenf[a[i]]:

=i;

  f[n+1]:

=m+1;

  fori:

=ndownto1do

  iff[i]=0thenf[i]:

=f[i+1];

  readln(s,t);

  spfa(s);

  writeln(d[t]);

  end.

例题1:

SweetButter香甜的黄油

描述

农夫John发现做出全威斯康辛州最甜的黄油的方法:

糖。

把糖放在一片牧场上,他知道N(1<=N<=500)只奶牛会过来舔它,这样就能做出能卖好价钱的超甜黄油。

当然,他将付出额外的费用在奶牛上。

农夫John很狡猾。

像以前的巴甫洛夫,他知道他可以训练这些奶牛,让它们在听到铃声时去一个特定的牧场。

他打算将糖放在那里然后下午发出铃声,以至他可以在晚上挤奶。

农夫John知道每只奶牛都在各自喜欢的牧场(一个牧场不一定只有一头牛)。

给出各头牛在的牧场和牧场间的路线,找出使所有牛到达的路程和最短的牧场(他将把糖放在那)

 

格式

PROGRAMNAME:

butter

INPUTFORMAT:

(filebutter.in)

第一行:

三个数:

奶牛数N,牧场数P(2<=P<=800),牧场间道路数C(1<=C<=1450)

第二行到第N+1行:

1到N头奶牛所在的牧场号

第N+2行到第N+C+1行:

每行有三个数:

相连的牧场A、B,两牧场间距离D(1<=D<=255),当然,连接是双向的

OUTPUTFORMAT:

(filebutter.out)

一行输出奶牛必须行走的最小的距离和

SAMPLEINPUT

345

2

3

4

121

135

237

243

345

programbutter;

var

   f1,f2:

text;

   n,p,c:

longint;

   count:

array[1..800]oflongint;

   a,b:

array[1..800,0..800]oflongint;

   d:

array[1..20000]ofinteger;

   v:

array[1..800]ofboolean;

   dist:

array[1..800]oflongint;

   head,tail:

longint;

   ans:

longint;

procedureinit;

   var

     i,j,x,y,z:

longint;

   begin

     assign(f1,'butter.in');reset(f1);

     assign(f2,'butter.out');rewrite(f2);

     readln(f1,N,P,C);

     fillchar(count,sizeof(count),0);

     fori:

=1tondobegin

       read(f1,x);

       inc(count[x]);                                            

     end;                                             

     fori:

=1topdo

       forj:

=1topdo

         a[i,j]:

=maxlongint;    

     fori:

=1tocdobegin

       read(f1,x,y,z);                                       

       inc(b[x,0]);b[x,b[x,0]]:

=y;a[x,y]:

=z;       

       inc(b[y,0]);b[y,b[y,0]]:

=x;a[y,x]:

=z;     

     end;  

   end;

procedurespfa(s:

longint);

   var

     i,j,now,sum:

longint;  

   begin

     fillchar(d,sizeof(d),0);

     fillchar(v,sizeof(v),false);

     fori:

=1topdodist[i]:

=maxlongint;

     dist[s]:

=0;v[s]:

=true;d[1]:

=s;

     head:

=1;tail:

=1;

     whilehead<=taildobegin

       now:

=d[head];

       fori:

=1tob[now,0]do                    

         ifdist[b[now,i]]>dist[now]+a[now,b[now,i]]thenbegin

        dist[b[now,i]]:

=dist[now]+a[now,b[now,i]];          

      ifnotv[b[now,i]]thenbegin                   

        inc(tail);                     

        d[tail]:

=b[now,i];

        v[b[now,i]]:

=true;                   

        end;                              

      end;

       v[now]:

=false;

       inc(head);   

     end;

     sum:

=0;

     fori:

=1topdo

       ifcount[i]<>0then

         inc(sum,count[i]*dist[i]);       

  ifans>sumthenans:

=sum;  

   end;

proceduremain;

   var

     i:

longint;  

   begin  

     ans:

=maxlongint;  

     fori:

=1topdospfa(i);

   end;

begin

   init;

   main;

   writeln(f2,ans);

   close(f2);

end.

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 法律文书 > 调解书

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2