1. 程式人生 > >最短路——spfa

最短路——spfa

求單源最短路的SPFA演算法的全稱是:Shortest Path Faster Algorithm。 
    SPFA演算法是西南交通大學段凡丁於1994年發表的。
    從名字我們就可以看出,這種演算法在效率上一定有過人之處。 
    很多時候,給定的圖存在負權邊,這時類似Dijkstra等演算法便沒有了用武之地,而Bellman-Ford演算法的複雜度又過高,SPFA演算法便派上用場了。有人稱spfa演算法是最短路的萬能演算法。

    簡潔起見,我們約定有向加權圖G不存在負權迴路,即最短路徑一定存在。當然,我們可以在執行該演算法前做一次拓撲排序,以判斷是否存在負權迴路。
    我們用陣列dis記錄每個結點的最短路徑估計值,可以用鄰接矩陣或鄰接表來儲存圖G,推薦使用鄰接表。

spfa的演算法思想(動態逼近法):
    設立一個先進先出的佇列q用來儲存待優化的結點,優化時每次取出隊首結點u,並且用u點當前的最短路徑估計值對離開u點所指向的結點v進行鬆弛操作,如果v點的最短路徑估計值有所調整,且v點不在當前的佇列中,就將v點放入隊尾。這樣不斷從佇列中取出結點來進行鬆弛操作,直至佇列空為止。 
    鬆弛操作的原理是著名的定理:“三角形兩邊之和大於第三邊”,在資訊學中我們叫它三角不等式。所謂對結點i,j進行鬆弛,就是判定是否dis[j]>dis[i]+w[i,j],如果該式成立則將dis[j]減小到dis[i]+w[i,j],否則不動。 
    下面舉一個例項來說明SFFA演算法是怎樣進行的:




和廣搜bfs的區別:
    SPFA 在形式上和廣度(寬度)優先搜尋非常類似,不同的是bfs中一個點出了佇列就不可能重新進入佇列,但是SPFA中一個點可能在出佇列之後再次被放入佇列,也就是一個點改進過其它的點之後,過了一段時間可能本身被改進(重新入隊),於是再次用來改進其它的點,這樣反覆迭代下去。

演算法描述:

void spfa(int s)
{
    memset(vis,0,sizeof(vis));//vis[i]=1表示i點在佇列q中,反之不在
    memeset(dis,inf,sizeof(dis));//dis[i]表示i點到源點的最短距離
    queue<int>q;
    q.push(s);
    vis[s]=1;
    while(!q.empty())
    {
       int u=q.front();
       q.pop();
       vis[u]=0;
       for(i=head[u];i!=-1;i=eg[i].next)//eg[]是一個連結串列儲存點和邊的資訊
       {
           int v=eg[i].to;
           int w=eg[i].w;
           if(dis[v]>dis[u]+w)
           {
               ans=min(ans,d[v]+d[u]);
               dis[v]=dis[u]+w;
               if(!vis[v])
               {
                  q.push(v);
                  vis[v]=1;
               }
           }
       }
    }
}

例題連結