探秘SPFA——強大的單源最短路徑算法
基於上次發blog,有位朋友讓我多寫些基本概念,就利用這次詳解偉大的SPFA算法來談。以下是百科上的算法簡介,很清楚,看一遍再繼續對理解程序很有幫助!(當然後面我也會解釋)
SPFA(Shortest Path Faster Algorithm)(隊列優化)算法是求單源最短路徑的一種算法,它還有一個重要的功能是判負環(在差分約束系統中會得以體現),在Bellman-ford算法的基礎上加上一個隊列優化,減少了冗余的松弛操作,是一種高效的最短路算法。
求單源最短路的SPFA算法的全稱是:Shortest Path Faster Algorithm,是西南交通大學段凡丁於1994年發表的(中國人的算法就是牛)。從名字我們就可以看出,這種算法在效率上一定有過人之處。很多時候,給定的圖存在負權邊,這時類似Dijkstra算法等便沒有了用武之地,而Bellman-Ford算法的復雜度又過高,SPFA算法便派上用場了。簡潔起見,我們約定加權有向圖G不存在負權回路,即最短路徑一定存在。如果某個點進入隊列的次數超過N次則存在負環(SPFA無法處理帶負環的圖)。當然,我們可以在執行該算法前做一次拓撲排序,以判斷是否存在負權回路,但這不是我們討論的重點。我們用數組d記錄每個結點的最短路徑估計值,而且用鄰接表來存儲圖G。我們采取的方法是動態逼近法:設立一個先進先出的隊列用來保存待優化的結點,優化時每次取出隊首結點u,並且用u點當前的最短路徑估計值對離開u點所指向的結點v進行松弛操作,如果v點的最短路徑估計值有所調整,且v點不在當前的隊列中,就將v點放入隊尾。這樣不斷從隊列中取出結點來進行松弛操作,直至隊列空為止。
順便解釋一下“松弛”:松弛操作是指對於每個頂點v∈V,都設置一個屬性d[v],用來描述從源點s到v的最短路徑上權值的上界,稱為最短路徑估計(shortest-path estimate)。 ————摘自《百度百科》
它的定義在上面第一句話解釋的不能再簡潔了,理解上述就好了。
至少我認為,SPFA算法是所有單源最短路算法中最實用的一種。(大佬們可以有其他想法,在此僅表示本人觀點)
還是用一道求最短路徑的模板題來解釋:
題目描述
【題意】 給出一個圖,起始點是1,結束點是N,邊是雙向的。求點1到點N的最短距離。哈哈,這就是標準的最短路徑問題。 【輸入格式】 第一行為兩個整數N(1≤N≤10000)和M(0≤M≤200000)。N表示圖中點的數目,M表示圖中邊的數目。 下來M行,每行三個整數x,y,c表示點x到點y之間存在一條邊長度為c。(x≠y,1≤c≤10000) 【輸出格式】 輸出一行,一個整數,即為點1到點N的最短距離。 如果點1和點N不聯通則輸出-1。 【樣例1輸入】 2 1 1 2 3 【樣例1輸出】 3【樣例2輸入】 3 3 1 2 5 2 3 5 3 1 2 【樣例2輸出】 2
【樣例3輸入】 6 9 1 2 7 1 3 9 1 5 14 2 3 10 2 4 15 3 4 11 3 5 2 4 6 6 5 6 9 【樣例3輸出】 20 【數據規模】 30%:1<=n<=100 50%:1<=n<=1000 100%:1<=n<=10000
故事:如何所有點(包括終點)到出發點的距離最短(最近)。 1、給出一個圖有N個點,和一些有向邊(無向邊也行,多建立一個反向邊就是) 2、一開始出發點到出發點的距離為0,其它點到出發點的距離為無窮大。 3、核心思路:其他點都在迫切的想知道自己到出發點的距離,並且他們都想自己的好朋友能更近一點到出發點(更新自己的好朋友到出發點的距離) 4、核心思路:一個點什麽時候能更新自己好朋友到出發點的距離呢?當自己到出發點的距離變得更短的時候。 5、核心思路:我們建一個隊列q,讓能更新別人的點站到q裏面。然後讓q中的點一個一個出來更新。 6、最後沒人出來更新了,就結束,表示所有點到出發點的距離都是最短了。
我用的是模擬鏈表存圖,你用其他的鄰接矩陣也可以(要看點的規模,一般太大用鄰接矩陣很劃不來)
來吧,不廢話,上代碼:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int N = 10005, M = 200005, oo = 0x3fffffff; //N表示最大點的個數 ,M表示最大邊的個數 ,oo是無窮大
struct Edge{
int to,wei,next; //鄰接鏈表的套路,to鄰接頂點,wei表邊的權重,next表鏈表指針
};
Edge edge[M]; //儲存邊的信息
int n,m,source,head[N],x,y,c,en(0),dist[N]; //dist【i】表示起點到 i的最短距離
bool inq[N];
queue<int> q;
void Addedge(int x,int y,int c) //存圖 ,x到y有一條權重為c的邊
{
edge[en].to=y;
edge[en].wei=c;
edge[en].next=head[x];
head[x]=en++;
}
void init()
{
cin>>n>>m;
memset(head,-1,sizeof(head)); //清零,作為每個點dfs的終止標誌
fill(inq,inq+n+1,false); //一開始都不在隊列中
while (m--)
{
cin>>x>>y>>c;
Addedge(x,y,c);
Addedge(y,x,c);
}
fill(dist,dist+n+1,oo); //先初始化為無窮大
source=1; //起點
}
void spfa() //這是套路
{
q.push(source);
dist[source]=0;
inq[source]=true;
while (!q.empty())
{
int u=q.front();
q.pop();
inq[u]=false;
for (int p=head[u];p!=-1;p=edge[p].next) //遍歷整張圖
{
int v=edge[p].to;
if (dist[v]>dist[u]+edge[p].wei) //如果到v的距離大於到u再加上u到v的距離,就更新
{
dist[v]=dist[u]+edge[p].wei;
if (inq[v]!=true)
{
q.push(v);
inq[v]=true; //指標記,防止重復進入,否則隊列沒有意義
}
}
}
}
}
void output()
{
cout<<dist[n]<<endl; //輸出到終點的距離就行了,其實你想輸出到哪點的最短距離都可以
}
int main()
{
init();//輸入存圖
spfa();//算法核心
output();//輸出答案
return 0;
}
代碼很簡潔,希望大家理解!
了解SPFA是十分有用的,如果認為自己掌握的不錯,就可以去做[NOIP提高組2009]最優貿易。提示:正反兩遍SPFA。
題解附上,最好先不看:
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;
const int N = 100005, M = 500005;
struct Edge{
int to,next;
};
Edge edge[4*M];
int n,m,en(0),price[N],head1[N],head2[N],buy[N],sell[N];
bool inq[N];
queue< int > q;
void insert( int head[], int x, int y){
edge[en].to = y;
edge[en].next = head[x];
head[x] = en++;
}
void init(){
//freopen("trade.in","r",stdin);
scanf ( "%d%d" ,&n,&m);
for ( int i=1;i<=n;i++) scanf ( "%d" ,&price[i]);
memset (head1,-1, sizeof (head1));
memset (head2,-1, sizeof (head2));
for ( int i=0,x,y,z;i<m;i++){
scanf ( "%d%d%d" ,&x,&y,&z);
insert(head1,x,y);
insert(head2,y,x);
if (z==2){
insert(head1,y,x);
insert(head2,x,y);
}
}
}
void spfa1(){
int u,v;
memset (buy,0x3f, sizeof (buy));
memset (inq,0, sizeof (inq));
buy[1] = price[1];
q.push(1) ;
inq[1] = true ;
while (!q.empty() ){
u = q.front() ;
q.pop();
inq[u] = false ;
for ( int p=head1[u]; p!=-1; p=edge[p].next ){
v = edge[p].to ;
if (min(buy[u],price[v])<buy[v]){
buy[v] = min(buy[u],price[v]);
if (!inq[v]){
q.push(v);
inq[v] = true ;
}
}
}
}
}
void spfa2(){
int u,v;
memset (sell,0, sizeof (sell));
memset (inq,0, sizeof (inq));
sell[n] = price[n];
q.push(n);
inq[n] = true ;
while (!q.empty() ){
u = q.front();
q.pop();
inq[u] = false ;
for ( int p=head2[u]; p!=-1; p=edge[p].next ){
v = edge[p].to;
if (max(sell[u],price[v])>sell[v]){
sell[v] = max(sell[u],price[v]);
if (!inq[v]){
q.push(v) ;
inq[v] = true ;
}
}
}
}
}
int main(){
init();
spfa1();
spfa2();
int ans=0;
for ( int i=1;i<=n;i++)
ans = max(ans,sell[i]-buy[i]);
//freopen("trade.out","w",stdout);
printf ( "%d\n" ,ans);
return 0;
}
|
探秘SPFA——強大的單源最短路徑算法