1. 程式人生 > >論最短路徑問題!!!(NOIP必備)

論最短路徑問題!!!(NOIP必備)

題目連結:

難度一:P3371 【模板】單源最短路徑(弱化版)

難度二:P4779 【模板】單源最短路徑(標準版) (慎入)

題目簡述 給出一個有向圖,請輸出從某一點出發到所有點的最短路徑長度。

 

輸入格式: 第一行包含三個整數N、M、S,分別表示點的個數、有向邊的個數、出發點的編號。

        接下來M行每行包含三個整數Fi、Gi、Wi,分別表示第i條有向邊的出發點、目標點和長度。

輸出格式 :僅一行,包含N個用空格分隔的整數,其中第i個整數表示從點S出發到點i的最短路徑長度(若S=i則最短路徑長度為0,若從點S無法到達點i,則最短路徑長度為2147483647)

樣例資料

輸入:

4 6 1
1 2 2 2 3 2 2 4 1 1 3 5 3 4 3 1 4 4

輸出:

0 2 4 3

 

常見的圖論解法有4種(⊙o⊙)哦

第一種:Floyd演算法

  思想:

    Floyd演算法是一個經典的動態規劃演算法。用通俗的語言來描述的話,首先我們的目標是尋找從點i到點j的最短路徑。從動態規劃的角度看問題,我們需要為這個目標重新做一個詮釋(這個詮釋正是動態規劃最富創造力的精華所在)

    從任意節點i到任意節點j的最短路徑不外乎2種可能,一是直接從i到j;二是從i經過若干個節點k到j。所以,我們假設Dis(i,j)為節點u到節點v的最短路徑的距離,對於每一個節點k,我們檢查Dis(i,k) + Dis(k,j) < Dis(i,j)是否成立,如果成立,證明從i到k再到j的路徑比i直接到j的路徑短,我們便設定Dis(i,j) = Dis(i,k) + Dis(k,j),這樣一來,當我們遍歷完所有節點k,Dis(i,j)中記錄的便是i到j的最短路徑的距離。

 

  優點:

    容易理解,可以算出任意兩個節點之間的最短距離,程式碼編寫簡單。並可以處理負邊權問題

 

  缺點:

    時間複雜度比較高,不適合計算大量資料。時間複雜度O(n^3),空間複雜度O(n^2)。且不能處理負環迴路問題

 

  初始化&&輸入:

      Floyd常用鄰接矩陣(二維陣列)來儲存;即F[i][j]儲存從點 i 到點 j 的距離dis

     值得一提的是:在初始化時,要將所有的點存為+∞,表示從點 i 到點 j 無法到達,再將F[i][j]賦值為 0(i==j)

     所有的 Dis[i]=MAXN;Dis[S]=0;//詳見下樓程式碼

  程式碼實現:

1 for(L k=1; k<=N; ++k)//列舉中間點
2 for(L i=1; i<=N; ++i )//列舉起點 3 for(L j=1; j<=N; ++j)//列舉終點 4 if(F[i][j]+ANS[i]<ANS[j]) //若經過中間點的距離小於已知最短的距離!!! 5   ANS[j]=F[i][j]+ANS[i];//更新資料 

  完整程式碼:

 1 //Floyd版
 2 #include<algorithm>
 3 #include<cstdio>
 4 #include<cstdlib>  5 #include<cstring>  6 #include<iostream>  7 using namespace std;  8 typedef long long L;  9 const L MAXN=2147483647; 10 L F[10010][10010],ANS[10010]; 11 inline L read()//快速讀入,PS:可以直接用scanf,此次是筆者打習慣了 12 { 13 L w=0,x=0; 14 char ch=0; 15 while(!isdigit(ch))w|=ch=='-',ch=getchar(); 16 while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar(); 17 return w?-x:x; 18 } 19 int main(void) 20 { 21 L N=read(),M=read(),S=read(); 22 for(L i=1; i<=N; ++i)//初始化 23  { 24 for(L j=1; j<=N; ++j) F[i][j]=(i==j?0:MAXN); 25 ANS[i]=MAXN; 26  } 27 for(L i=1; i<=M; ++i)//輸入 28  { 29 L A=read(),B=read(),l=read(); 30 F[A][B]=min(F[A][B],l); 31  } 32 ANS[S]=0,F[1][1]=0; 33 for(L k=1; k<=N; ++k)//Floyd演算法實現 34 for(L i=1; i<=N; ++i ) 35 for(L j=1; j<=N; ++j) 36 if(F[i][j]+ANS[i]<ANS[j]) 37 ANS[j]=F[i][j]+ANS[i]; 38 for(L i=1; i<=N; ++i) printf("%lld ",ANS[i]);//輸出 39 return 0; 40 } 41 

 

   評價:

    這種演算法雖然時間複雜度較高,但勝在容易理解,並且程式碼簡便,清晰。在其他演算法不會時可以騙個30分。(⊙o⊙)嗯!!!

 

 第二種:Dijkstra演算法

  基本思想:

    Dijkstra(迪傑斯特拉)演算法是典型的單源最短路徑演算法,用於計算一個節點到其他所有節點的最短路徑。主要特點是以起始點為中心向外層層擴充套件,直到擴充套件到終點為止。

  演算法步驟:

    1.初始時, From 只包含源點,即 From={S} 。 U 包含除 S 外的其他頂點,即:U={其餘頂點}。 f 為 From 內一點, u 為 U 內一點。若從 f 能到 u 則 MAP[i] 。[j]!=MAXN,若f不能到 u ,則 MAP[i][j] 權值為 MAXN 。

    2.從 U 中選取一個距離 S 最小的頂點 f ,把 f ,加入 From 中(該選定的距離就是 S 到 f 的最短路徑長度)。

    3.以  f  為新考慮的中間點,修改 U 中各頂點的距離:若從起點 S 到頂點 u 的距離(經過頂點 f )比原來距離(不經過頂點 f )短,則修改頂點 u 的距離值,修改後的距離值的頂點 f 的距離加上邊上的權。

      4.重複步驟 2 和 3 直到所有頂點都包含在 From 中。

    

  未優化版:

      1.鄰接矩陣儲存:

        

//Dijkstra版(未優化)
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring> #include<iostream> using namespace std; typedef long long L; const L MAXN=2147483647; L MAP[10010][10010],Dis[10010]; bool from[10010]; inline L read() //快讀 { L w=0,x=0; char ch=0; while(!isdigit(ch))w|=ch=='-',ch=getchar(); while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar(); return w?-x:x; } int main(void) { L N=read(),M=read(),S=read(); for(L i=1; i<=N; ++i)//初始化  { for(L j=1; j<=N; ++j) MAP[i][j]=(i==j?0:MAXN); Dis[i]=(i==S)?0:MAXN; } for(L i=1; i<=M; ++i) //輸入  { L f=read(),to=read(),dis=read(); MAP[f][to]=min(W,MAP[F][G]); } for(L i=1; i<N; ++i)//共有N個頂點,除終點外還有N-1 個頂點  { L MINN=MAXN,f=0; for(L j=1; j<=N; ++j) //查詢從S到u的最短路 if( (!from[j] ) && (Dis[j]<MINN) )//(!from[j])因為必須是從U中取出的頂點 MINN=Dis[j],f=j; from[f]=true;//記錄這點已在From中 for(L j=1; j<=N; ++j) Dis[j]=(Dis[f]+MAP[f][j]<Dis[j])?Dis[f]+MAP[f][j]:Dis[j];//更新資料  } for(L i=1; i<=N; ++i) printf("%lld ",Dis[i]);//輸出 return 0; } 

      2.鄰接表儲存(前向星):(前向星的會比較難懂一點,大家儘量看看嘛。畢竟後面的堆優化是要用到的,而且光一個前向星就能少很多空間了)

 1 #include<algorithm>
 2 #include<cstdio>
 3 #include<cstdlib>
 4 #include<cstring>
 5 #include<iostream>
 6 using namespace std;  7 typedef long long L;  8 const L MAXN=2147483647;  9 struct Edge//前向星 10 { 11 L next;//下一條邊的編號 12 L to;//這條邊到達的點 13 L dis;//這條邊的長度 14 } E[500010]; 15 L num_edge=0,Head[10010],Dis[10010]; 16 bool from[10010]; 17 inline L read() //快讀 18 { 19 L w=0,x=0; 20 char ch=0; 21 while(!isdigit(ch))w|=ch=='-',ch=getchar(); 22 while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar(); 23 return w?-x:x; 24 } 25 inline void Add_edge(L from,L to,L d)//前向星插入 26 { 27 ++num_edge; 28 E[num_edge].next=Head[from]; 29 E[num_edge].to=to; 30 E[num_edge].dis=d; 31 Head[from]=num_edge; 32 } 33 int main(void) 34 { 35 L N=read(),M=read(),S=read(); 36 for(int i=1; i<=N; ++i) Dis[i]=(i==S)?0:MAXN;//初始化 37 for(L i=1; i<=M; ++i)//輸入 38  { 39 L u=read(),v=read(),d=read(); 40  Add_edge(u,v,d); 41  } 42 L f=S; 43 while(!from[f]) 44  { 45 from[f]=true; 46 47 for(L i=Head[f]; i!=0; i=E[i].next)//修改點距離 48 if( !from[E[i].to ] && (Dis[f]+E[i].dis<Dis[E[i].to]) ) 49 Dis[E[i].to]=Dis[f]+E[i].dis;//更新 50 51 L MINN=MAXN; 52 for(L i=1; i<=N; ++i)//尋找最小點 53  { 54 55 if(!from[i]&&Dis[i]<MINN) 56  { 57 MINN=Dis[i]; 58 f=i; 59  } 60  } 61  } 62 for(L i=1; i<=N; i++)printf("%lld ",Dis[i]);//輸出 63 return 0; 64 }

 

  堆優化版:

    其中的 pair  參見: C++ std::pair的用法

    priority_queue優先佇列參見:STL--priority_queue用法

    堆優化的原理網上大佬們都講得不大好,本弱雞也講不清,個人建議是背下來。の平時一些不會的大難題也是這麼幹的。O(∩_∩)O~

 1 //Dijkstra版(堆優化)
 2 #include<algorithm>
 3 #include<cstdio>
 4 #include<cstdlib>  5 #include<cstring>  6 #include<iostream>  7 #include<queue>  8 #include<vector>  9 using namespace std; 10 typedef long long L; 11 typedef pair<L,L> node; 12 const L MAXN=2147483647;//最大值 13 struct Edge 14 { 15 L next;//下一條邊的編號 16 L to;//這條邊到達的點 17 L dis;//這條邊的長度 18 } E[500010]; 19 L num_edge=0,Head[10010],Dis[10010],N,M,S; 20 bool Map[10010]; 21 inline L read() //快讀 22 { 23 L w=0,x=0; 24 char ch=0; 25 while(!isdigit(ch))w|=ch=='-',ch=getchar(); 26 while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar(); 27 return w?-x:x; 28 } 29 inline void Add_edge(L from,L to,L d)//前向星儲存試插入 30 { 31 ++num_edge; 32 E[num_edge].next=Head[from]; 33 E[num_edge].to=to; 34 E[num_edge].dis=d; 35 Head[from]=num_edge; 36 } 37 inline void Scanf()//輸入與初始化 38 { 39 N=read(); 40 L M=read(),S=read(); 41 for(int i=1; i<=N; ++i) Dis[i]=(i==S)?0:MAXN; 42 for(L i=1; i<=M; ++i) 43  { 44 L u=read(),v=read(),d=read(); 45  Add_edge(u,v,d); 46  } 47 } 48 inline void Printf()//輸出答案 49 { 50 for(L i=1; i<=N; i++)printf("%lld ",Dis[i]); 51 52 } 53 inline void Dijkstra()//Dijkstra 演算法實現 54 { 55 priority_queue< node,vector<node>,greater<node> > T;//小根堆 56 57 T.push(make_pair(0,S)); 58 59 while(!T.empty()) 60  { 61 L f=T.top().second; 62  T.pop(); 63 for(L i=Head[f]; i!=0; i=E[i].next) 64  { 65 if(Dis[f]+E[i].dis<Dis[E[i].to]) 66  { 67 Dis[E[i].to]=Dis[f]+E[i].dis; 68  T.push(make_pair(Dis[E[i].to],E[i].to)); 69  } 70  } 71  } 72 } 73 int main(void) 74 { 75 Scanf(); 76 Dijkstra(); 77 Printf(); 78 return 0; 79 }