图论算法(二)最短路算法:Floyd算法!
最短路算法(一)
最短路算法有三種形態:Floyd算法,Shortset Path Fast Algorithm(SPFA)算法,Dijkstra算法。
我個人打算分三次把這三個算法介紹完。
(畢竟寫太長了又沒有人看QAQ……)但是這篇博客好像又雙叒叕寫的有點長,真的請各位耐心看完QAQ
今天先來介紹最簡單的Floyd算法。
Part 1:最短路問題是什么?
我們用專業一點的術語表達,大概是這樣子的:
若網絡中的每條邊都有一個數值(長度、成本、時間等),則找出兩節點(通常是源節點和阱節點)之間總權和最小的路徑就是最短路問題。
——摘自百度百科
但是完全不用關那些個專業的東西,我們通過字面意思大概就能Get到——求出某個點走到某個點之間的權值之和最小。
比如我們有一張圖:
比如我們要求出從點1到點5的最短路就是這樣的
點1->點2(走了4),點2->點5(走了1),這樣點1到點5的最短距離就是5,(因為找不到比5更短的路可以從點1到點5)最短路就是1->4->5。
Part 2:Floyd算法思路
在介紹Floyd算法之前,我們先思考這樣一個問題:
有一張圖,我們要求出最短路。怎么做?
我們可以間接的把它看成一個DP來搞,那么狀態就是這樣的:
我們枚舉點k,i,j,并假設i是起始點。
如果i->j的當前最短路長度 > i->k的最短路長度+k->j的最短路長度,我們就更新i->j的最短路長度是i->k+k->j。
什么意思呢?仔細思考一下,得出如下結果:
我們如果從某點直接到x點比先到y點再到x點的路徑之和還要長的話,當然就要更新“某點”到x點的最短距離啦!
對于這個思想,科學家已經給出了證明,我這里不再贅述。
(以下是證明過程,反正我是看不懂,但是NOI又不考算法為什么對,其實我們只要知道算法的正確性和怎么應用就好了,至于證明,那是數學家的事情)
Part 3:Floyd算法的各項性能數據、適用范圍、初始化注意事項
我們知道了Floyd算法的大致框架了,在康代碼之前,還有一個不能忽略的問題:它的適用范圍是什么。
(畢竟最短路有三種算法,算法競賽的時候不一定考哪種,萬一用錯了算法……)
適用范圍:存在負權邊但是沒有負權回路的有向圖、無向圖。(所有算法都不能解決負權回路,因為那樣根本不存在最短路)
時間復雜度O(n^3)時間復雜度要特別注意,當有500個點的時候就已經很危險了。
空間復雜度O(n^2)鄰接矩陣限制了空間復雜度不能再優化了。
結果調用方法:存在鄰接矩陣里,其中f[i][j]表示i->j的最短路長度。
算法主體代碼長度(不包括初始化等等):150B。
整個求最短路代碼長度:約600B(已經是最短路算法中最短的了)。
初始化注意事項:先把鄰接矩陣初始化為0x3f,然后把所有f[i][i]初始化為0。
Part 4:Floyd算法結構框架
具體思路和注意事項我上面已經講得很清楚了,我這里直接上模板代碼。
#include<algorithm>
#include<cstring>
#include<cstdio>
#define N 1010
void floyd()
{
for(int k=1;k<=n;k++)//枚舉中間點k(一定一定是在最外層循環!)
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
dist[i][j]=max(dist[i][j],dist[i][k]+dist[k][j]);
//如果更優,更新最優解
}
int main()
{
memset(dist,0x3f,sizeof(dist));//memset初始化
scanf("%d%d",&n,&m);//n點m邊圖
for(int i=1;i<=m;i++)
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
dist[x][y]=z;
dist[y][x]=z;//初始化無向圖鄰接矩陣
}
for(int i=1;i<=n;i++)
dist[i][i]=0;//對角線重新賦值為0
floyd();//調用Floyd解決最短路問題
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
printf("%d ",dist[i][j]);//輸出所有i->j的最短路徑
}
printf("
");
}
return 0;
}
Part 5:Floyd算法在實際問題中的應用
我在某洛谷上找到了這樣兩個題,很適合初學最短路算法的選手:
https://www.luogu.com.cn/problem/P2910
https://www.luogu.com.cn/problem/P1828
值得注意的是:這兩個題目不一定要用Floyd算法來解決,但是我們今天拿這兩個題簡單說說Floyd的實現注意事項。
首先看第一個題:洛谷P2910
雖然有超鏈接,我還是把題目的圖貼上來吧。
這個題目沒有直白的說“求最短路”(可能是板子題最后的尊嚴)但是明眼人都看出來了,出題人把權值抽象成了“危險程度”,規定了幾個節點是必須訪問的,求從出發點經過這些必須訪問的節點的最短路徑長度。
讀懂了題意之后,思考這樣一個問題:我們要用什么算法做這個題(這很重要!再次重申,最短路算法有三個,我們要選出最能對付這個題,在AC的前提下找出寫起來最簡單的算法)
首先看空間復雜度:點的個數N<=100也就是說,滿足鄰接矩陣的空間復雜度和時間復雜度。
必須到達的點有M<=10000個,也就是說,我們要求10000次單源最短路,Floyd算法可以一次性求出一張圖內任意兩點間的最短路。
綜上所述,這個題滿足使用Floyd算法的要求,并且我們說過,Floyd算法是最好寫的單源最短路算法(沒有之一!!!)
具體思路簡單BB一下,我們求出最短路之后,用存下“必須經過的點”的數組,把相鄰元素的最短路一次次累加,最后就可以得到最終答案。
上AC代碼:
//#include<guxinlin&sutang>
//GXL AK IOI
#include<algorithm>
#include<cstring>
#include<cstdio>
#define N 110
#define M 10010
#define sutang 0
using namespace std;
int v[N][N],vis[M],dis[N][N],n,m,ans;
void floyd()
{
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
}
int main()
{
scanf("%d%d",&n,&m);
memset(dis,0x3f,sizeof(dis));
for(int i=1;i<=m;i++)
scanf("%d",&vis[i]);
for(int i=1;i<=n;i++)
{
dis[i][i]=0;
for(int j=1;j<=n;j++)
{
scanf("%d",&v[i][j]);
dis[i][j]=v[i][j];
}
}
floyd();
for(int i=2;i<=m;i++)
ans+=dis[vis[i-1]][vis[i]];
printf("%d",ans);
return sutang;
}
下一個題目,洛谷P1828
先上題目截圖:
一眼看過去,節點數P<=800,800^3=512000000(5億一千二百萬)這個復雜度已經很危險了,但是為什么我們用Floyd算法AC掉了這個題呢?
下面是重點部分了,另外,上面的題的正解是n次堆優化dijkstra算法或者n次SPFA算法。
Floyd靈魂剪枝算法
其實這個題我本來沒有用Floyd算法做,(我是用的SPFA通過的評測)但是我看到題解去的大神們寫了這樣一篇題解,提到了Floyd靈魂剪枝。
我覺得是很有用的一個小技巧,而且實現起來非常簡單。于是我就把這個小技巧分享給大家。
具體思路是:如果給出的圖是無向邊,我們用鄰接矩陣存圖的時候是把無向邊當成兩條相反、長度相等的有向邊來存的,這就導致我們循環枚舉的的時候,更新了兩次這個無向邊,浪費了寶貴的時間。所以當圖是無向圖的時候,我們只需要枚舉一半的邊,更新的時候一下子更新兩條邊的最短距離,就可以優化一半的復雜度。(但是這個復雜度還是很高,只是一個小技巧,不是大優化)
說完了剪枝策略,我們來說說這個題的具體思路:
首先,每個節點可能有多個牛,我們就需要把第i頭牛在第j個牧場記下來。
其次,我們需要找出一個點,使其到這些有奶牛的點距離之和最小,確認是最短路算法。我們不知道哪個點距離和最小,所以我們需要把所有點到所有點的單源最短路求出來,然后枚舉每一個點的距離之和,找出最小值。找出所有點的單源最短路,Floyd算法可以做到。加上我們的剪枝策略,再加上O2優化,Floyd算法可以在1000ms內給出結果。
#include<algorithm>
#include<cstring>
#include<cstdio>
#define IAKIOI 0
#define N 1010
using namespace std;
int cow[N],dist[N][N],n,m,q,ans=99999999;
void floyd()
{
for(int k=1;k<=n;k++)
{
for(int i=1;i<=n;i++)
{
for(int j=1;j<=i;j++)//枚舉一半,剩下的手動更新
{
if(dist[i][j]>dist[i][k]+dist[k][j])
{
dist[i][j]=dist[i][k]+dist[k][j];
dist[j][i]=dist[i][j];//手動更新另一條邊
}
}
}
}
}
int main()
{
memset(dist,0x3f,sizeof(dist));//初始化
scanf("%d%d%d",&q,&n,&m);
for(int i=1;i<=q;i++)//記錄第i頭牛所在牧場是cow[i]
scanf("%d",&cow[i]);
for(int i=1;i<=m;i++)
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
dist[x][y]=z;//數據里沒有重邊,所以不用特判(其實是我忘了
dist[y][x]=z;
}
for(int i=1;i<=n;i++)//初始化
dist[i][i]=0;
floyd();//調用Floyd解決問題
for(int i=1;i<=n;i++)
{
int qaq=0;
for(int j=1;j<=q;j++)//枚舉第i個點的
{
qaq+=dist[i][cow[j]];//累加第i個點的總路程
}
ans=min(ans,qaq);//如果比最優答案還優,更新他
}
printf("%d",ans);//輸出答案
return IAKIOI;
}
好了今天的最短路學習分享就到這里,喜歡的記得三連QAQ(卑微博主求關注)
總結
以上是生活随笔為你收集整理的图论算法(二)最短路算法:Floyd算法!的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 工行信用卡申请多久能拿到
- 下一篇: 戏曲服饰如何分类