【最短路徑】 常用算法圖解+1376:信使(msner)六解
進入圖之後,最短路徑可謂就是一大重點,最短路徑的求法有很多種,每種算法各有各的好處,你會幾種呢?下面來逐個講解。
1 floyed算法
1)明確思想及功效:在圖中求最短路還是要分開說的,分別是單源最短路和多源最短路,而floyed算法是求多源最短路的,什麽是多源最短路呢?簡單來說就是用完算法之後能直接寫出任意兩點間的最短路徑長度。floyed算法在本質上是動態規劃思想,不斷更新最短路徑的值,主要思想就是不斷判斷兩個點是否可以通過一個點中繼以刷新當前兩個點最短路徑的估計值,直到每兩個點都判斷完成。很容易理解,就不詳細圖解了。
2)代碼:
1 for(int k=1;k<=n;k++) 2 for(int i=1;i<=n;i++) 3 for(int j=1;j<=n;j++) 4 { 5 if(a[i][j]>a[i][k]+a[k][j]) 6 { 7 a[i][j]=a[i][k]+a[k][j]; 8 } 9 10 }
3)時間復雜度:O(n^3)
2 dijkstra算法
1)思想及其功效:dijkstra算法屬於單源最短路算法,簡單說就是用過之後,只能直接寫出一點到其他任意一點的最短路徑長度。這個算法類似於prim算法,找出刷新點所能到達的點,然後再繼續刷新,寫起來太難理解,自己都不會說了,那就具體以一幅圖來講解。
如果這個圖是這樣的:
當然,圖要用矩陣來存儲。因為是單源最短路,只能求一個點到其他各點的最短距離,所以我們可以假設是求1號點到其他各點的最短距離。此時我們需要一個數組dis[ ]來存儲1到5個頂點分別的最短路徑長度。那麽,在最開始時,數組裏初始化為估計值,凡是1能到達的點,相應路徑的長度會直接存在數組中,而不能直接到達的點則被當做正無窮,可以賦值成999999或0x3f3f3f3f等盡可能大的數,具體是什麽樣呢?如圖所示:
思考幾個問題:
- 為什麽dis[1]的值是0?
answer:因為dis[ ]數組所存的是1到各點的最短路徑長度,1到1不用走,直接就到了呀,所以是0;
- OO是什麽?
answer:因為畫圖軟件不會畫無限符號,就用這個代替正無窮吧,寫代碼時可以賦成盡可能大的值。
- 為什麽有的賦值了而有的沒有?
answer:如上面所說,比如2和5,從圖上看它們都和1有直接連邊,可以賦進dis裏,但是3和4與1無直接連邊,只能通過其他點間接到達,所以先賦估計值正無窮。
接著就把剛才刷新的2和5作為中繼看一看是否可以刷新其他點的值。
先來看一看通過2所能刷新的點,分別是3和5,dis[3]原本是正無窮,現在通過2只需要權值5就能到達,比正無窮小,就更新它,像這種操作,專業術語叫做“松弛”,同理,繼續松弛操作,dis[5]的值本來為8,因為通過2來中繼,3+4<8,所以更新它,繼續這樣操作不斷循環下去,直到所有未知點全部被訪問並進行松弛操作。
//藍邊表示中繼,紅邊代表這條邊不能用,綠邊表示可以通過這條邊來刷新。
剛才刷新了3和4,現在通過3和4來中繼。
通過3無法繼續刷新,繼續看4。
依舊沒點刷新,此時,所有點都訪問過了,並進行了松弛操作所有的dis[ ]全部從估計值一步步變成了確定值。
算法演示結束。
2)主要問題
- 怎麽確定所有頂點被訪問過?
answer:可以定義一個vis[ ]數組,最開始全是0,每選出一個點,就把vis的值更新為1。
- 先找哪個點為中繼?
answer:先選一條沒有訪問過且dis值最小的。
- 有代碼嗎?
answer:有,如下:
1 void dijkstra() 2 { 3 4 vis[1]==0;dis[1]=0; 5 for(int i=1;i<=n-1;i++) 6 { 7 minn=inf; 8 for(int j=1;j<=n;j++) 9 { 10 if(vis[j]==0&&dis[j]<minn) 11 { 12 k=j; 13 minn=dis[j]; 14 } 15 } 16 vis[k]=1; 17 for(int j=1;j<=n;j++) 18 { 19 if(dis[j]>dis[k]+map[k][j]&&map[k][j]<inf) 20 dis[j]=dis[k]+map[k][j]; 21 } 22 } 23 }
3)時間復雜度:O(n^2 )
3 SPFA
圖的權值有正的,當然也有負的,對於負邊權,之前學過的算法都無法處理,而SPFA則能處理負權邊。思路和dijkstra有點像,具體圖解,將會下一期帶來。先放個代碼湊個數:
void SPFA() { for(int i=1;i<=n;i++) dis[i]=inf; queue<int>q; q.push(1);vis[1]=1;dis[1]=0; while(q.size()) { x=q.front();q.pop();vis[x]=0; for(int i=head[x];i;i=a[i].next) { int s=a[i].to; if(dis[s]>dis[x]+a[i].cost) { dis[s]=dis[x]+a[i].cost; if(vis[s]==0) { vis[s]=1; q.push(s); } } } } }
4 例題講解:
1376:信使(msner)
時間限制: 1000 ms 內存限制: 65536 KB
提交數: 1847 通過數: 879
【題目描述】
戰爭時期,前線有n個哨所,每個哨所可能會與其他若幹個哨所之間有通信聯系。信使負責在哨所之間傳遞信息,當然,這是要花費一定時間的(以天為單位)。指揮部設在第一個哨所。當指揮部下達一個命令後,指揮部就派出若幹個信使向與指揮部相連的哨所送信。當一個哨所接到信後,這個哨所內的信使們也以同樣的方式向其他哨所送信。直至所有n個哨所全部接到命令後,送信才算成功。因為準備充足,每個哨所內都安排了足夠的信使(如果一個哨所與其他k個哨所有通信聯系的話,這個哨所內至少會配備k個信使)。
現在總指揮請你編一個程序,計算出完成整個送信過程最短需要多少時間。
【輸入】
第1行有兩個整數n和m,中間用1個空格隔開,分別表示有n個哨所和m條通信線路,且1≤n≤100。
第2至m+1行:每行三個整數i、j、k,中間用1個空格隔開,表示第i個和第j個哨所之間存在通信線路,且這條線路要花費k天。
【輸出】
一個整數,表示完成整個送信過程的最短時間。如果不是所有的哨所都能收到信,就輸出-1。
【輸入樣例】
4 4 1 2 4 2 3 7 2 4 1 3 4 6
【輸出樣例】
11
這道題一看感覺沒思路,就盲目的先寫一遍floyed,再看看有沒有什麽規律,寫著寫著就想通了,既然每兩個哨所之間都可以直接/間接的通信,而且起點是從第一個哨所開始的,那麽整個時間當然就是1號哨所到達其他哨所的最短路徑中最大的,應為其他路徑在執行此路徑時也在同時進行,廢話不多說,代碼如下:
//floyed 版1376:信使(msner)
1 #include<iostream> 2 using namespace std; 3 int a[1000][1000],n,m,d,b,c;int maxn=-1; 4 int main() 5 { 6 cin>>n>>m; 7 for(int i=1;i<=n;i++) 8 for(int j=1;j<=n;j++)//初始賦成盡可能大的值 9 a[i][j]=999999; 10 for(int i=1;i<=n;i++) 11 a[i][i]=0; 12 for(int i=1;i<=m;i++) 13 { 14 cin>>d>>b>>c; 15 a[d][b]=c; 16 a[b][d]=c; 17 } 18 for(int k=1;k<=n;k++) 19 for(int i=1;i<=n;i++) 20 for(int j=1;j<=n;j++) 21 { 22 if(a[i][j]>a[i][k]+a[k][j])//floyed算法 23 { 24 a[i][j]=a[i][k]+a[k][j]; 25 } 26 27 } 28 for(int i=1;i<=n;i++) 29 { 30 if(maxn<a[1][i])//選出最大的路徑 31 maxn=a[1][i]; 32 } 33 cout<<maxn; 34 return 0; 35 }
//dijksra版1376:信使(msner)
1 #include<iostream> 2 #define inf 999999 3 using namespace std; 4 int n,m,map[1000][1000],dis[1000],vis[1000],a,b,maxn=-1,minn,k; 5 inline void dijkstra() 6 { 7 8 vis[1]==0;dis[1]=0; 9 for(int i=1;i<=n-1;i++) 10 { 11 minn=inf; 12 for(int j=1;j<=n;j++) 13 { 14 if(vis[j]==0&&dis[j]<minn) 15 { 16 k=j; 17 minn=dis[j]; 18 } 19 } 20 vis[k]=1; 21 for(int j=1;j<=n;j++) 22 { 23 if(dis[j]>dis[k]+map[k][j]&&map[k][j]<inf) 24 dis[j]=dis[k]+map[k][j]; 25 } 26 } 27 } 28 int main() 29 { 30 cin>>n>>m; 31 for(int i=1;i<=n;i++) 32 { 33 for(int j=1;j<=n;j++) 34 map[i][j]=inf; 35 dis[i]=inf; 36 } 37 for(int i=1;i<=m;i++) 38 { 39 cin>>a>>b; 40 cin>>map[a][b]; 41 map[b][a]=map[a][b]; 42 } 43 for(int i=1;i<=n;i++) 44 dis[i]=map[1][i]; 45 dijkstra(); 46 for(int i=1;i<=n;i++) 47 { 48 if(dis[i]>maxn) 49 maxn=dis[i]; 50 } 51 cout<<maxn; 52 return 0; 53 }
//SPFA版 1376:信使(msner)
1 #include<iostream> 2 #include<cstdio> 3 #include<queue> 4 #define inf 999999 5 using namespace std; 6 struct map{ 7 int from; 8 int to; 9 int cost; 10 int next; 11 }a[1000]; 12 int head[1000],dis[1000],vis[1000],n,m,maxn=-1,x,cnt,a1,b1,c1; 13 void add(int from,int to,int cost) 14 { 15 a[++cnt].to=to; 16 a[cnt].cost=cost; 17 a[cnt].next=head[from]; 18 head[from]=cnt; 19 } 20 void SPFA() 21 { 22 for(int i=1;i<=n;i++) 23 dis[i]=inf; 24 queue<int>q; 25 q.push(1);vis[1]=1;dis[1]=0; 26 while(q.size()) 27 { 28 x=q.front();q.pop();vis[x]=0; 29 for(int i=head[x];i;i=a[i].next) 30 { 31 int s=a[i].to; 32 if(dis[s]>dis[x]+a[i].cost) 33 { 34 dis[s]=dis[x]+a[i].cost; 35 if(vis[s]==0) 36 { 37 vis[s]=1; 38 q.push(s); 39 } 40 } 41 } 42 } 43 } 44 int main() 45 { 46 scanf("%d%d",&n,&m); 47 for(int i=1;i<=m;i++) 48 { 49 scanf("%d%d%d",&a1,&b1,&c1); 50 add(a1,b1,c1); 51 add(b1,a1,c1); 52 } 53 54 SPFA(); 55 for(int i=2;i<=n;i++) 56 if(dis[i]>maxn&&dis[i]!=inf) maxn=dis[i]; 57 printf("%d",maxn); 58 return 0; 59 }
//SPFA+SLF版 1376:信使(msner)
1 #include<iostream> 2 #include<cstdio> 3 #include<queue> 4 #include<cstring> 5 #define inf 0x3f3f3f3f 6 using namespace std; 7 int dis[500000],head[500000]={0},vis[500000]={0},n,m,s,x,a,b,c,cnt,maxn; 8 struct edge{ 9 int from; 10 int to; 11 int value; 12 int next; 13 }map[500000]; 14 inline void add(int from,int to,int value) 15 { 16 map[++cnt].to=to; 17 map[cnt].value=value; 18 map[cnt].next=head[from]; 19 head[from]=cnt; 20 } 21 void SPFA() 22 { 23 memset(dis,inf,sizeof(dis)); 24 deque<int>q; 25 q.push_back(1);dis[1]=0;vis[1]=1; 26 while(q.size()) 27 { 28 x=q.front();q.pop_front();vis[x]=0; 29 for(int i=head[x];i;i=map[i].next) 30 { 31 s=map[i].to; 32 if(dis[s]>dis[x]+map[i].value) 33 { 34 dis[s]=dis[x]+map[i].value; 35 if(vis[s]==0) 36 { 37 if(dis[s]<dis[q.front()]) q.push_front(s); 38 else q.push_back(s); 39 vis[s]=1; 40 } 41 } 42 } 43 } 44 } 45 int main() 46 { 47 scanf("%d%d",&n,&m); 48 for(int i=1;i<=m;i++) 49 { 50 scanf("%d%d%d",&a,&b,&c); 51 add(a,b,c); 52 add(b,a,c); 53 } 54 SPFA(); 55 for(int i=1;i<=n;i++) 56 if(dis[i]>maxn) maxn=dis[i]; 57 cout<<maxn; 58 return 0; 59 }
//SPFA+LLL版 1376:信使(msner)
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<queue> 5 #define inf 0x3f3f3f3f 6 using namespace std; 7 int dis[500000],head[500000]={0},vis[500000]={0},n,m,sum,x,y,a,b,c,cnt,p,s,maxn=-1,cnt_2=1; 8 struct edge{ 9 int from; 10 int to; 11 int value; 12 int next; 13 }map[500000]; 14 inline void add(int from,int to,int value) 15 { 16 map[++cnt].to=to; 17 map[cnt].value=value; 18 map[cnt].next=head[from]; 19 head[from]=cnt; 20 } 21 void SPFA() 22 { 23 memset(dis,inf,sizeof(dis)); 24 queue<int>q; 25 q.push(1);dis[1]=0;vis[1]=1; 26 while(q.size()) 27 { 28 p=q.front();q.pop(); 29 if(dis[p]*cnt_2>sum) 30 { 31 q.push(p); 32 continue; 33 } 34 sum-=dis[p];cnt_2--; 35 vis[p]=0;; 36 for(int i=head[p];i;i=map[i].next) 37 { 38 s=map[i].to; 39 if(dis[s]>dis[p]+map[i].value) 40 { 41 dis[s]=dis[p]+map[i].value; 42 if(vis[s]==0) 43 { 44 vis[s]==1; 45 q.push(s); 46 cnt_2++; 47 sum+=dis[s]; 48 } 49 } 50 } 51 } 52 } 53 int main() 54 { 55 scanf("%d%d",&n,&m); 56 for(int i=1;i<=m;i++) 57 { 58 scanf("%d%d%d",&a,&b,&c); 59 add(a,b,c); 60 add(b,a,c); 61 } 62 SPFA(); 63 for(int i=1;i<=n;i++) 64 if(maxn<dis[i]) maxn=dis[i]; 65 printf("%d",maxn); 66 return 0; 67 }
//SPFA+SLF+LLL版 1376:信使(msner)
#include<iostream> #include<cstdio> #include<cstring> #include<queue> #define inf 0x3f3f3f3f using namespace std; int dis[500000],head[500000]={0},vis[500000]={0},n,m,sum,x,y,a,b,c,cnt,p,s,maxn=-1,cnt_2=1; struct edge{ int from; int to; int value; int next; }map[500000]; inline void add(int from,int to,int value) { map[++cnt].to=to; map[cnt].value=value; map[cnt].next=head[from]; head[from]=cnt; } void SPFA() { memset(dis,inf,sizeof(dis)); deque<int>q; q.push_back(1);dis[1]=0;vis[1]=1; while(q.size()) { p=q.front();q.pop_front(); if(dis[p]*cnt_2>sum) { q.push_back(p); continue; } sum-=dis[p];cnt_2--; vis[p]=0;; for(int i=head[p];i;i=map[i].next) { s=map[i].to; if(dis[s]>dis[p]+map[i].value) { dis[s]=dis[p]+map[i].value; if(vis[s]==0) { vis[s]==1; if(dis[s]<dis[q.front()]) q.push_front(s); else q.push_back(s); cnt_2++; sum+=dis[s]; } } } } } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) { scanf("%d%d%d",&a,&b,&c); add(a,b,c); add(b,a,c); } SPFA(); for(int i=1;i<=n;i++) if(maxn<dis[i]) maxn=dis[i]; printf("%d",maxn); return 0; }
寫是確實寫了六次,看看你會寫幾種?但是想著應該是越往後越快呀,後面有用到優化,可是結果堪憂啊。
//SPFA三種優化方法
//SPFA、dijkstra、floyed
天哪!合著我這是優化的越來越慢了,看來優化也得慎重啊。下一期將會重點講述如何優化SPFA。
【最短路徑】 常用算法圖解+1376:信使(msner)六解