Floyd算法詳(cha)解

Floyd 算法應該是最基本,最簡單,也是最暴力的最短路算法解法,可是對於一些點數很小的題目,Floyd的表現仍是很優秀的,咱們先來看一道例題算法

題目描述給你一個有 \(n\) (\(n\leq 100\)) 個點以及 \(m\) (\(m\leq 800\)) 條雙向邊的圖,求出全部點之間的最短路。
輸入格式:第一行兩個正整數 \(n\),\(m\),接下來 \(m\) 行,每行三個正整數 \(u\),\(v\),\(w\),表示 \(u\)\(v\) 之間有一條代價(長度)爲 \(w\) 的邊。
輸出格式:輸出共 \(n\) 行,每行 \(n\) 個正整數,第 \(i\) 行第 \(j\) 個數,表示點 \(i\) 到點 \(j\) 之間的最短路。
樣例輸入
5 6
1 3 7
2 4 5
2 3 1
4 3 2
1 5 8
5 2 4
樣例輸出
0 8 7 9 8
8 0 1 3 4
7 1 0 2 5
9 3 1 0 7
8 4 5 7 0數組

對於任何一個操做,咱們均可以分紅三個部分:1.選擇操做容器,2.初始化,3.更新,操做。spa

好比這一題,讓咱們求出 全部點 之間的最短路,並以一個 矩陣型 輸出,因此咱們能夠考慮就如題目中的那樣,用這個臨接矩陣來儲存,咱們把這個矩陣稱爲 \(\operatorname{dis}\),用 \(\operatorname{dis[i][j]}\) 來表示 \(\operatorname{i\sim j}\) 之間的最短路徑。3d

知道了容器,咱們就要考慮如何初始化。由於題目讓咱們求全部點之間的最短路,因此咱們能夠初始化 \(\operatorname{dis[i][i]=0}\) (能夠理解爲,我到我本身,至關於沒動,因此我不須要耗費任何代價),而後每輸入兩個點 \(u\),\(v\),就將他們輸入的代價,做爲最初的 \(\operatorname{dis[u][v]}\) 設置爲代價 \(w\),由於在如今看來,這條路是最短的,咱們沒法從其餘地方更新。而後咱們考慮一個問題,若是這不是一個 徹底圖,也就是說,若是並非每兩個點之間都有一條連邊,有一些數對(i,j)是沒有直接通路的,咱們要怎麼辦?其實很是簡單,若是兩個點一開始沒有直接的連邊,咱們就能夠將它設置成一個不會被重複的值(通常爲正無窮或者負無窮,看求的東西),也就是 \(\operatorname{dis[u][v]=Inf}\),因而咱們在最開始,就賦值整個數組爲正無窮,這樣就能夠很方便的預處理完成最初的每兩個點之間的最短路了。code

可是,咱們這樣並不能直接求出這整個圖每兩點之間的最短路,由於確定有一些點沒有更新到,而且有一些點之間的最短路點在最後不必定是最短的,這個很好證實,我就不贅述了。因而咱們考慮更新,咱們拿樣例打比方,咱們先建出這個圖:
blog

而後按照剛剛的方法初始化一下這個矩陣每一個點之間的最初的最短路,大概是這個樣子的:get

0 ∞ ∞ ∞ ∞ --> 0 ∞ 7 ∞ 8
∞ 0 ∞ ∞ ∞ --> ∞ 0 1 5 4
∞ ∞ 0 ∞ ∞ --> 7 1 0 2 ∞
∞ ∞ ∞ 0 ∞ --> ∞ 5 2 0 ∞
∞ ∞ ∞ ∞ 0 --> 8 4 ∞ ∞ 0class

而後就是這個算法最重要的部分了,咱們考慮如何更新兩個點之間的最短路,來看下面這個簡化的圖:

在這個圖中,\(1\sim3\) 之間初試化的最短路是 \(10000\),顯然,當咱們從新選擇 \(1\sim 2\sim3\) 這條路的時候,代價就減少到了 \(3\),比以前那條道路更優秀。這就至關因而在點 \(1\)\(3\) 之間,找到一個新的點加入,用 \(1\sim 2\)\(2\sim3\) 來更新這個最短路,能夠算做是一種 區間dp 的思想。Floyd 的核心思想就是這個,就是在 兩點之間加上一個點 而後和以前的最短路做比較,而後不斷更新,達到求出全源最短路的效果。容器

咱們再將這種算法放回原樣例中去驗證一下,咱們從1-n 枚舉每一個節點 \(k\) ,用來更新兩個點之間是否還有更短的路徑。咱們從 \(1\) 開始,咱們來看一遍。方法

用 1 更新:
0 ∞ 7 ∞ 8 --> 0 ∞ 7 ∞ 8
∞ 0 1 5 4 --> ∞ 0 1 5 4
7 1 0 2 ∞ --> 7 1 0 2 ∞
∞ 5 2 0 ∞ --> ∞ 5 2 0 ∞
8 4 ∞ ∞ 0 --> 8 4 ∞ ∞ 0

用 2 更新:
0 ∞ 7 ∞ 8 --> 0 ∞ 7 ∞ 8
∞ 0 1 5 4 --> ∞ 0 1 5 4
7 1 0 2 ∞ --> 7 1 0 2 5
∞ 5 2 0 ∞ --> ∞ 5 2 0 9
8 4 ∞ ∞ 0 --> 8 4 5 9 0

用 3 更新:
0 ∞ 7 ∞ 8 --> 0 8 7 9 8
∞ 0 1 5 4 --> 8 0 1 3 4
7 1 0 2 5 --> 7 1 0 2 5
∞ 5 2 0 9 --> 9 3 2 0 7
8 4 ∞ ∞ 0 --> 8 4 5 7 0

用 4 更新:
0 8 7 9 8 --> 0 8 7 9 8
8 0 1 3 4 --> 8 0 1 3 4
7 1 0 2 5 --> 7 1 0 2 5
9 3 2 0 7 --> 9 3 2 0 7
8 4 5 7 0 --> 8 4 5 7 0

用 5 更新:
0 8 7 9 8 --> 0 8 7 9 8
8 0 1 3 4 --> 8 0 1 3 4
7 1 0 2 5 --> 7 1 0 2 5
9 3 2 0 7 --> 9 3 2 0 7
8 4 5 7 0 --> 8 4 5 7 0

(你們能夠配着圖來理解)[加粗數字爲更新過的最短路]

看起來沒什麼問題實際也沒什麼問題,因而咱們開是碼代碼吧。

先是初始化:

memset(dis,0x3f3f3f3f,sizeof(dis));
for(int i=1;i<=n;i++)dis[i][i]=0;
for(int i=1;i<=n;i++)int u,v,w,scanf("%d%d%d",&u,&v,&w),dis[u][v]=w;

第一行就是初始化 \(\operatorname{dis}\) 都爲正無窮,0x3f3f3f3f是16進制中的一個數,差很少接近了 INT_MAX 了。
第二行就是本身到本身的最短路
第三行是輸入與每兩個點之間的最短路初始化。

而後就是 Floyd 啦:

for(int k=1;k<=n;k++)//首先枚舉中間加入的點,否則會出錯
      for(int i=1;i<=n;i++)//而後i,j是枚舉每一個點,算 i~j 之間的最短路 
            for(int j=1;j<=n;j++)
                 dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);//Floyd的狀態轉移方程,這是精華,必定要記牢

這些上面已經講過了,沒懂的能夠多看幾遍。
看懂了的完整代碼應該不難寫了,因此整塊的代碼就不放了。

下面給出幾個例題:

相關文章
相關標籤/搜索