單源最短路_spfa

SPFA

適用范圍:
  給定的圖存在負權邊辽俗,這時類似Dijkstra等算法便沒有了用武之地,而Bellman-Ford算法的復雜度又過高篡诽,SPFA算法便派上用場了崖飘。 我們約定有向加權圖G不存在負權回路,即最短路徑一定存在杈女。
  期望的時間復雜度O(ke)朱浴, 其中k為所有頂點進隊的平均次數(shù),可以證明k一般小于等于2达椰。

判斷有無負環(huán):
  如果某個點進入隊列的次數(shù)超過N次則存在負環(huán)

算法偽代碼:

 procedure Shortest-Path-Faster-Algorithm(G, s)
  1    for each vertex v ≠ s in V(G)
  2        d(v) := ∞
  3    d(s) := 0
  4    offer s into Q
  5    while Q is not empty
  6        u := poll Q
  7        for each edge (u, v) in E(G)
  8            if d(u) + w(u, v) < d(v) then
  9                d(v) := d(u) + w(u, v)
 10                if v is not in Q then
 11                    offer v into Q

P3371 【模板】單源最短路徑
題意:
求出起點到每個點的最短路徑

#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
using namespace std;
const int MAXN=10010;
const int MAXE=500010;
const int INF=0x7fffffff;
struct Node
{
    int to,next,val;
};
Node edge[MAXE];
int cnt,head[MAXN];
int dis[MAXN],inque[MAXN];//判斷是否在隊列中
void addEdge(int u,int v,int val)
{
    edge[cnt].to=v;edge[cnt].val=val;
    edge[cnt].next=head[u];head[u]=cnt++;
}
void spfa_bfs(int st)
{
    memset(inque,0,sizeof(inque));
    dis[st]=0;
    queue<int> que;
    que.push(st);
    int u,v,i;
    while(!que.empty())
    {
        u=que.front();que.pop();
        inque[u]=0;
        for(i=head[u];i!=-1;i=edge[i].next)
        {
            v=edge[i].to;
            if(dis[v]>dis[u]+edge[i].val)
            {
                dis[v]=dis[u]+edge[i].val;
                if(!inque[v])
                {
                    que.push(v);
                    inque[v]=1;
                }
            }
        }
    }
}
int main()
{
    int n,m,st,u,v,val;
    scanf("%d%d%d",&n,&m,&st);
    fill(dis+1,dis+1+n,INF);
    memset(head,-1,sizeof(head));
    cnt=0;
    for(int i=0;i<m;i++)
    {
        scanf("%d%d%d",&u,&v,&val);
        addEdge(u,v,val);
    }
    spfa_bfs(st);
    bool start=true;
    for(int i=1;i<=n;i++)
    {
        if(!start) printf(" ");
        printf("%d",dis[i]);
        start=false;
    }
    printf("\n");
}

Wormholes
題意:
圖是連通的,判斷負環(huán)
題解:
任選一個起點進行spfa

#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
using namespace std;
const int MAXN=10010;
const int MAXE=500010;
const int INF=0x3f3f3f3f;
struct Node
{
    int to,next,val;
};
Node edge[MAXE];
int cnt,head[MAXN];
int dis[MAXN],inque[MAXN];
int queNum[MAXN];//入隊次數(shù)
void addEdge(int u,int v,int val)
{
    edge[cnt].to=v;edge[cnt].val=val;
    edge[cnt].next=head[u];head[u]=cnt++;
}
bool spfa_bfs(int st,int n)
{
    memset(inque,0,sizeof(inque));
    memset(dis,INF,sizeof(dis));
    memset(queNum,0,sizeof(queNum));
    dis[st]=0;
    queue<int> que;
    que.push(st);
    queNum[st]++;
    int u,v,i;
    while(!que.empty())
    {
        u=que.front();que.pop();
        inque[u]=0;
        for(i=head[u];i!=-1;i=edge[i].next)
        {
            v=edge[i].to;
            if(dis[v]>dis[u]+edge[i].val)
            {
                dis[v]=dis[u]+edge[i].val;
                if(!inque[v])
                {
                    que.push(v);
                    queNum[v]++;
                    if(queNum[v]>n) return true;//有負環(huán)
                    inque[v]=1;
                }
            }
        }
    }
    return false;
}
int main()
{
    int n,m,k,u,v,val,t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d%d",&n,&m,&k);
        memset(head,-1,sizeof(head));
        cnt=0;
        for(int i=0;i<m;i++)
        {
            scanf("%d%d%d",&u,&v,&val);
            addEdge(u,v,val);
            addEdge(v,u,val);
        }
        for(int i=0;i<k;i++)
        {
            scanf("%d%d%d",&u,&v,&val);
            addEdge(u,v,-val);
        }
        if(spfa_bfs(1,n)) printf("YES\n");
        else printf("NO\n");
    }
}

其實,spfa可以寫成DFS的形式,只不過是把隊列換成了棧!同時,用bfs形式的spfa來判斷負環(huán)會很慢,因為對于有負環(huán)的情況我們必須在某個點入隊n次后才能判斷出來翰蠢,如果n很大,那會非常耗時,而用DFS可以改善,因為DFS不會重復將一個點入棧,而是將下一個點入棧

看一看判負環(huán)的方法:

  • 1、在spfa同時記錄當前節(jié)點是否在棧中
  • 2啰劲、如果某節(jié)點可被當前節(jié)點松弛
    • 該節(jié)點還在棧中梁沧,說明松弛路徑出現(xiàn)環(huán),退出
    • 否則以該節(jié)點為當前點進行深搜spfa操作

但是,如果明確知道圖中沒有負環(huán),只是求最短路徑,還是要用BFS的spfa,DFS棧太深跑得比較慢!

這里先給出spfa_dfs求最短路的代碼(其實已經(jīng)可以判負環(huán)了)
暢通工程續(xù)
題意:
給出起點終點,求最短路

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int MAXN=10010;
const int MAXE=500010;
const int INF=0x7fffffff;
struct Node
{
    int to,next,val;
};
Node edge[MAXE];
int cnt,head[MAXN];
int dis[MAXN],instack[MAXN];//判斷是否在棧中
void addEdge(int u,int v,int val)
{
    edge[cnt].to=v;edge[cnt].val=val;
    edge[cnt].next=head[u];head[u]=cnt++;
}
bool spfa_dfs(int u)
{
    instack[u]=1;
    for(int i=head[u];i!=-1;i=edge[i].next)
    {
        int v=edge[i].to;
        if(dis[v]>dis[u]+edge[i].val)
        {
            dis[v]=dis[u]+edge[i].val;
            if(!instack[v])
            {
                if(spfa_dfs(v)) return true;//有負環(huán)
            }
            else return true;//有負環(huán)
        }
    }
    instack[u]=0;
    return false;
}
int main()
{
    int n,m,st,ed,u,v,val;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        memset(head,-1,sizeof(head));
        cnt=0;
        for(int i=0;i<m;i++)
        {
            scanf("%d%d%d",&u,&v,&val);
            addEdge(u,v,val);
            addEdge(v,u,val);
        }
        fill(dis,dis+n,INF);
        memset(instack,0,sizeof(instack));
        scanf("%d%d",&st,&ed);
        dis[st]=0;
        spfa_dfs(st);
        if(dis[ed]==INF) printf("-1\n");
        else printf("%d\n",dis[ed]);
    }
}

Wormholes
題意:
圖是連通的,判斷負環(huán)
題解:
任選一個起點進行spfa

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int MAXN=10010;
const int MAXE=500010;
const int INF=0x3f3f3f3f;
struct Node
{
    int to,next,val;
};
Node edge[MAXE];
int cnt,head[MAXN];
int dis[MAXN],inStack[MAXN];//判斷是否在棧中
void addEdge(int u,int v,int val)
{
    edge[cnt].to=v;edge[cnt].val=val;
    edge[cnt].next=head[u];head[u]=cnt++;
}
bool spfa_dfs(int u)
{
    inStack[u]=1;
    for(int i=head[u];i!=-1;i=edge[i].next)
    {
        int v=edge[i].to;
        if(dis[v]>dis[u]+edge[i].val)
        {
            dis[v]=dis[u]+edge[i].val;
            if(!inStack[v])
            {
                if(spfa_dfs(v)) return true;//有負環(huán)
            }
            else return true;//有負環(huán)
        }
    }
    inStack[u]=0;
    return false;
}
int main()
{
    int n,m,k,u,v,val,t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d%d",&n,&m,&k);
        memset(head,-1,sizeof(head));
        cnt=0;
        for(int i=0;i<m;i++)
        {
            scanf("%d%d%d",&u,&v,&val);
            addEdge(u,v,val);
            addEdge(v,u,val);
        }
        for(int i=0;i<k;i++)
        {
            scanf("%d%d%d",&u,&v,&val);
            addEdge(u,v,-val);
        }
        memset(inStack,0,sizeof(inStack));
        fill(dis+1,dis+1+n,INF);
        dis[1]=0;
        if(spfa_dfs(1)) printf("YES\n");
        else printf("NO\n");
    }
}

其實,如果只是判斷負環(huán),而不要求求最短路徑,dfs的spfa還有一個優(yōu)化的地方!
首先,如果存在負環(huán),那么肯定有某條邊是負數(shù)的,如果我們一開始就從負數(shù)邊進行spfa_dfs的話,那么肯定很快可以找到這個負環(huán),而且一旦找到負環(huán)就退出,這樣子會快很多!

那么一開始怎么找到負數(shù)邊呢??
  我們先看spfa_dfs中,現(xiàn)在進行第一次dfs拓展,要使得第一次選中負邊,假設我們把dis數(shù)組初始化為0,進行拓展時,dis[u]=0,dis[v]=0,要使得dis[v]>dis[u]+edge[i].val,那么邊必定是負的,然后就對負邊進行擴展了蝇裤。
  也就是說廷支,dis數(shù)組初始化為0。這樣處理后栓辜,第一次拓展只會拓展到與起點相連邊權為負的邊恋拍。
  當然,求判負環(huán)+求最短路時不能這樣初始化dis數(shù)組,因為有可能存在權重為負的路徑且不存在負環(huán),那么這樣的最短路是合理的,所以還是老老實實把dis數(shù)組初始化為∞藕甩。

P3385 【模板】負環(huán)
題意:
給一個圖,判斷是否有負環(huán)施敢。注意,圖不一定是聯(lián)通的,所以要通過多次不同起點單源最短判負環(huán)。(spfa_bfs會超時,spfa_dfs的dis數(shù)組不初始化為0也會超時)

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int MAXN=200010;
const int MAXE=200010*2;
const int INF=0x3f3f3f3f;
struct Node
{
    int to,next,val;
};
Node edge[MAXE];
int cnt,head[MAXN];
int dis[MAXN],inStack[MAXN];//判斷是否在棧中
void addEdge(int u,int v,int val)
{
    edge[cnt].to=v;edge[cnt].val=val;
    edge[cnt].next=head[u];head[u]=cnt++;
}
bool spfa_dfs(int u)
{
    inStack[u]=1;
    for(int i=head[u];i!=-1;i=edge[i].next)
    {
        int v=edge[i].to;
        if(dis[v]>dis[u]+edge[i].val)
        {
            dis[v]=dis[u]+edge[i].val;
            if(!inStack[v])
            {
                if(spfa_dfs(v)) return true;//有負環(huán)
            }
            else return true;//有負環(huán)
        }
    }
    inStack[u]=0;
    return false;
}
int main()
{
    int n,m,u,v,val,t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);
        memset(head,-1,sizeof(head));
        cnt=0;
        for(int i=0;i<m;i++)
        {
            scanf("%d%d%d",&u,&v,&val);
            addEdge(u,v,val);
            if(val>=0) addEdge(v,u,val);
        }
        memset(inStack,0,sizeof(inStack));
        memset(dis,0,sizeof(dis));
        bool flag=false;
        for(int i=1;i<=n;i++)
        {
            if(spfa_dfs(i))
            {
                flag=true;
                break;
            }
        }
        if(flag) printf("YE5\n");
        else printf("N0\n");
    }
}

另外,再給出一個用全局變量flag的dfs吧,其實和上面一樣的

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int MAXN=200010;
const int MAXE=200010*2;
const int INF=0x3f3f3f3f;
struct Node
{
    int to,next,val;
};
Node edge[MAXE];
int cnt,head[MAXN];
int dis[MAXN],inStack[MAXN];//判斷是否在棧中
void addEdge(int u,int v,int val)
{
    edge[cnt].to=v;edge[cnt].val=val;
    edge[cnt].next=head[u];head[u]=cnt++;
}
bool flag;
void spfa_dfs(int u)
{
    inStack[u]=1;
    for(int i=head[u];i!=-1;i=edge[i].next)
    {
        int v=edge[i].to;
        if(dis[v]>dis[u]+edge[i].val)
        {
            dis[v]=dis[u]+edge[i].val;
            if(!inStack[v])
            {
                spfa_dfs(v);
                if(flag) return;//有負環(huán)
            }
            else
            {
                flag=true;
                return;//有負環(huán)
            }
        }
    }
    inStack[u]=0;
}
int main()
{
    int n,m,u,v,val,t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);
        memset(head,-1,sizeof(head));
        cnt=0;
        for(int i=0;i<m;i++)
        {
            scanf("%d%d%d",&u,&v,&val);
            addEdge(u,v,val);
            if(val>=0) addEdge(v,u,val);
        }
        memset(inStack,0,sizeof(inStack));
        memset(dis,0,sizeof(dis));
        flag=false;
        for(int i=1;i<=n;i++)
        {
            spfa_dfs(i);
            if(flag) break;
        }
        if(flag) printf("YE5\n");
        else printf("N0\n");
    }
}
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市僵娃,隨后出現(xiàn)的幾起案子概作,更是在濱河造成了極大的恐慌,老刑警劉巖悯许,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件仆嗦,死亡現(xiàn)場離奇詭異,居然都是意外死亡先壕,警方通過查閱死者的電腦和手機瘩扼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來垃僚,“玉大人集绰,你說我怎么就攤上這事∽还祝” “怎么了栽燕?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長改淑。 經(jīng)常有香客問我碍岔,道長,這世上最難降的妖魔是什么朵夏? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任蔼啦,我火速辦了婚禮,結(jié)果婚禮上仰猖,老公的妹妹穿的比我還像新娘捏肢。我一直安慰自己,他們只是感情好饥侵,可當我...
    茶點故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布鸵赫。 她就那樣靜靜地躺著,像睡著了一般躏升。 火紅的嫁衣襯著肌膚如雪辩棒。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天煮甥,我揣著相機與錄音盗温,去河邊找鬼。 笑死成肘,一個胖子當著我的面吹牛卖局,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播双霍,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼砚偶,長吁一口氣:“原來是場噩夢啊……” “哼批销!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起染坯,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤均芽,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后单鹿,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體掀宋,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年仲锄,在試婚紗的時候發(fā)現(xiàn)自己被綠了劲妙。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡儒喊,死狀恐怖镣奋,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情怀愧,我是刑警寧澤侨颈,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站芯义,受9級特大地震影響哈垢,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜扛拨,卻給世界環(huán)境...
    茶點故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一温赔、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧鬼癣,春花似錦、人聲如沸啤贩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽痹屹。三九已至章郁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間志衍,已是汗流浹背暖庄。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留楼肪,地道東北人培廓。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像春叫,于是被迫代替她去往敵國和親肩钠。 傳聞我的和親對象是個殘疾皇子泣港,可洞房花燭夜當晚...
    茶點故事閱讀 45,685評論 2 360

推薦閱讀更多精彩內(nèi)容