最短路——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; } } } } }