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