論最短路徑問題!!!(NOIP必備)
題目連結:
難度二: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 }