Floyd-傻子也能看懂的弗洛伊德算法
?
? ? ? ?
?
暑假,小哼準(zhǔn)備去一些城市旅游。有些城市之間有公路,有些城市之間則沒有,如下圖。為了節(jié)省經(jīng)費(fèi)以及方便計(jì)劃旅程,小哼希望在出發(fā)之前知道任意兩個(gè)城市之前的最短路程。
?
?
? ?? ???上圖中有4個(gè)城市8條公路,公路上的數(shù)字表示這條公路的長(zhǎng)短。請(qǐng)注意這些公路是單向的。我們現(xiàn)在需要求任意兩個(gè)城市之間的最短路程,也就是求任意兩個(gè)點(diǎn)之間的最短路徑。這個(gè)問題這也被稱為“多源最短路徑”問題。
?
? ? ? ? 現(xiàn)在需要一個(gè)數(shù)據(jù)結(jié)構(gòu)來存儲(chǔ)圖的信息,我們?nèi)匀豢梢杂靡粋€(gè)4*4的矩陣(二維數(shù)組e)來存儲(chǔ)。比如1號(hào)城市到2號(hào)城市的路程為2,則設(shè)e[1][2]的值為2。2號(hào)城市無法到達(dá)4號(hào)城市,則設(shè)置e[2][4]的值為∞。另外此處約定一個(gè)城市自己是到自己的也是0,例如e[1][1]為0,具體如下。
? ? ? ??現(xiàn)在回到問題:如何求任意兩點(diǎn)之間最短路徑呢?通過之前的學(xué)習(xí)我們知道通過深度或廣度優(yōu)先搜索可以求出兩點(diǎn)之間的最短路徑。所以進(jìn)行n2遍深度或廣度優(yōu)先搜索,即對(duì)每?jī)蓚€(gè)點(diǎn)都進(jìn)行一次深度或廣度優(yōu)先搜索,便可以求得任意兩點(diǎn)之間的最短路徑。可是還有沒有別的方法呢?
?
? ?? ???我們來想一想,根據(jù)我們以往的經(jīng)驗(yàn),如果要讓任意兩點(diǎn)(例如從頂點(diǎn)a點(diǎn)到頂點(diǎn)b)之間的路程變短,只能引入第三個(gè)點(diǎn)(頂點(diǎn)k),并通過這個(gè)頂點(diǎn)k中轉(zhuǎn)即a->k->b,才可能縮短原來從頂點(diǎn)a點(diǎn)到頂點(diǎn)b的路程。那么這個(gè)中轉(zhuǎn)的頂點(diǎn)k是1~n中的哪個(gè)點(diǎn)呢?甚至有時(shí)候不只通過一個(gè)點(diǎn),而是經(jīng)過兩個(gè)點(diǎn)或者更多點(diǎn)中轉(zhuǎn)會(huì)更短,即a->k1->k2b->或者a->k1->k2…->k->i…->b。比如上圖中從4號(hào)城市到3號(hào)城市(4->3)的路程e[4][3]原本是12。如果只通過1號(hào)城市中轉(zhuǎn)(4->1->3),路程將縮短為11(e[4][1]+e[1][3]=5+6=11)。其實(shí)1號(hào)城市到3號(hào)城市也可以通過2號(hào)城市中轉(zhuǎn),使得1號(hào)到3號(hào)城市的路程縮短為5(e[1][2]+e[2][3]=2+3=5)。所以如果同時(shí)經(jīng)過1號(hào)和2號(hào)兩個(gè)城市中轉(zhuǎn)的話,從4號(hào)城市到3號(hào)城市的路程會(huì)進(jìn)一步縮短為10。通過這個(gè)的例子,我們發(fā)現(xiàn)每個(gè)頂點(diǎn)都有可能使得另外兩個(gè)頂點(diǎn)之間的路程變短。好,下面我們將這個(gè)問題一般化。
?
? ?? ???當(dāng)任意兩點(diǎn)之間不允許經(jīng)過第三個(gè)點(diǎn)時(shí),這些城市之間最短路程就是初始路程,如下。
? ? ? ?
?
?
假如現(xiàn)在只允許經(jīng)過1號(hào)頂點(diǎn),求任意兩點(diǎn)之間的最短路程,應(yīng)該如何求呢?只需判斷e[i][1]+e[1][j]是否比e[i][j]要小即可。e[i][j]表示的是從i號(hào)頂點(diǎn)到j(luò)號(hào)頂點(diǎn)之間的路程。e[i][1]+e[1][j]表示的是從i號(hào)頂點(diǎn)先到1號(hào)頂點(diǎn),再?gòu)?號(hào)頂點(diǎn)到j(luò)號(hào)頂點(diǎn)的路程之和。其中i是1~n循環(huán),j也是1~n循環(huán),代碼實(shí)現(xiàn)如下。
for (i = 1; i <= n; i++) {for (j = 1; j <= n; j++){if (e[i][j] > e[i][1] + e[1][j])e[i][j] = e[i][1] + e[1][j];} }在只允許經(jīng)過1號(hào)頂點(diǎn)的情況下,任意兩點(diǎn)之間的最短路程更新為:
?
? ?? ???通過上圖我們發(fā)現(xiàn):在只通過1號(hào)頂點(diǎn)中轉(zhuǎn)的情況下,3號(hào)頂點(diǎn)到2號(hào)頂點(diǎn)(e[3][2])、4號(hào)頂點(diǎn)到2號(hào)頂點(diǎn)(e[4][2])以及4號(hào)頂點(diǎn)到3號(hào)頂點(diǎn)(e[4][3])的路程都變短了。
?
? ?? ???接下來繼續(xù)求在只允許經(jīng)過1和2號(hào)兩個(gè)頂點(diǎn)的情況下任意兩點(diǎn)之間的最短路程。如何做呢?我們需要在只允許經(jīng)過1號(hào)頂點(diǎn)時(shí)任意兩點(diǎn)的最短路程的結(jié)果下,再判斷如果經(jīng)過2號(hào)頂點(diǎn)是否可以使得i號(hào)頂點(diǎn)到j(luò)號(hào)頂點(diǎn)之間的路程變得更短。即判斷e[i][2]+e[2][j]是否比e[i][j]要小,代碼實(shí)現(xiàn)為如下。
//經(jīng)過1號(hào)頂點(diǎn) for(i=1;i<=n;i++)for(j=1;j<=n;j++)if (e[i][j] > e[i][1]+e[1][j]) e[i][j]=e[i][1]+e[1][j]; //經(jīng)過2號(hào)頂點(diǎn) for(i=1;i<=n;i++)for(j=1;j<=n;j++)if (e[i][j] > e[i][2]+e[2][j]) e[i][j]=e[i][2]+e[2][j];在只允許經(jīng)過1和2號(hào)頂點(diǎn)的情況下,任意兩點(diǎn)之間的最短路程更新為:
? ?? ???通過上圖得知,在相比只允許通過1號(hào)頂點(diǎn)進(jìn)行中轉(zhuǎn)的情況下,這里允許通過1和2號(hào)頂點(diǎn)進(jìn)行中轉(zhuǎn),使得e[1][3]和e[4][3]的路程變得更短了。
?
? ?? ???同理,繼續(xù)在只允許經(jīng)過1、2和3號(hào)頂點(diǎn)進(jìn)行中轉(zhuǎn)的情況下,求任意兩點(diǎn)之間的最短路程。任意兩點(diǎn)之間的最短路程更新為:
? ?? ???最后允許通過所有頂點(diǎn)作為中轉(zhuǎn),任意兩點(diǎn)之間最終的最短路程為:
? ?? ???整個(gè)算法過程雖然說起來很麻煩,但是代碼實(shí)現(xiàn)卻非常簡(jiǎn)單,核心代碼只有五行:
for(k=1;k<=n;k++)for(i=1;i<=n;i++)for(j=1;j<=n;j++)if(e[i][j]>e[i][k]+e[k][j])e[i][j]=e[i][k]+e[k][j];這段代碼的基本思想就是:最開始只允許經(jīng)過1號(hào)頂點(diǎn)進(jìn)行中轉(zhuǎn),接下來只允許經(jīng)過1和2號(hào)頂點(diǎn)進(jìn)行中轉(zhuǎn)……允許經(jīng)過1~n號(hào)所有頂點(diǎn)進(jìn)行中轉(zhuǎn),求任意兩點(diǎn)之間的最短路程。用一句話概括就是:從i號(hào)頂點(diǎn)到j(luò)號(hào)頂點(diǎn)只經(jīng)過前k號(hào)點(diǎn)的最短路程。
#include int main() {int e[10][10],k,i,j,n,m,t1,t2,t3;int inf=99999999; //用inf(infinity的縮寫)存儲(chǔ)一個(gè)我們認(rèn)為的正無窮值//讀入n和m,n表示頂點(diǎn)個(gè)數(shù),m表示邊的條數(shù)scanf("%d %d",&n,&m);//初始化for(i=1;i<=n;i++)for(j=1;j<=n;j++)if(i==j) e[i][j]=0;else e[i][j]=inf;//讀入邊f(xié)or(i=1;i<=m;i++){scanf("%d %d %d",&t1,&t2,&t3);e[t1][t2]=t3;}//Floyd-Warshall算法核心語句for(k=1;k<=n;k++)for(i=1;i<=n;i++)for(j=1;j<=n;j++)if(e[i][j]>e[i][k]+e[k][j] )e[i][j]=e[i][k]+e[k][j];//輸出最終的結(jié)果for(i=1;i<=n;i++){for(j=1;j<=n;j++)printf("%10d",e[i][j]);printf("\n");}return 0; }? ?? ???另外需要注意的是:Floyd-Warshall算法不能解決帶有“負(fù)權(quán)回路”(或者叫“負(fù)權(quán)環(huán)”)的圖,因?yàn)閹в小柏?fù)權(quán)回路”的圖沒有最短路。例如下面這個(gè)圖就不存在1號(hào)頂點(diǎn)到3號(hào)頂點(diǎn)的最短路徑。因?yàn)?->2->3->1->2->3->…->1->2->3這樣路徑中,每繞一次1->-2>3這樣的環(huán),最短路就會(huì)減少1,永遠(yuǎn)找不到最短路。其實(shí)如果一個(gè)圖中帶有“負(fù)權(quán)回路”那么這個(gè)圖則沒有最短路。
迪杰斯特拉算法要求所有邊的權(quán)值均非負(fù),弗洛伊德算法允許圖中有帶負(fù)權(quán)值的邊,但不允許有包含帶負(fù)權(quán)值的邊組成的回路。
總結(jié)
以上是生活随笔為你收集整理的Floyd-傻子也能看懂的弗洛伊德算法的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: BFS求无权图的单源最短路径-邻接矩阵存
- 下一篇: 求最小生成树-Prim(普里姆算法)