短小精悍的多源最短路徑算法—Floyd算法

前言

圖論中,在尋路最短路徑中除了Dijkstra算法以外,還有Floyd算法也是非常經典,然而兩種算法還是有區別的,Floyd主要計算多源最短路徑。

在單源正權值最短路徑,我們會用Dijkstra算法來求最短路徑,並且算法的思想很簡單——貪心算法:每次確定最短路徑的一個點然後維護(更新)這個點周圍點的距離加入預選隊列,等待下一次的拋出確定。但是雖然思想很簡單,實現起來是非常複雜的,我們需要鄰接矩陣(表)儲存長度,需要優先隊列(或者每次都比較)維護一個預選點的集合。還要用一個boolean數組標記是否已經確定、還要---------

總之,Dijkstra算法的思想上是很容易接受的,但是實現上其實是非常麻煩的。但是單源最短路徑沒有更好的辦法。複雜度也爲O(n2)

而在n節點多源最短路徑中,如果從Dijkstra算法的角度上,只需要將Dijkstra封裝,然後執行n次Dijkstra算法即可,複雜度爲O(n3)。但是這樣感覺很臃腫,代碼量巨大,佔用很多空間內存。有沒有啥方法能夠稍微變變口味呢?

答案是有的,這就是易寫但稍需要理解的Floyd算法。一個求多元最短路徑算法。

算法介紹

先看看百度百科的定義吧:

Floyd算法又稱爲插點法,是一種利用動態規劃的思想尋找給定的加權圖中多源點之間最短路徑的算法,與Dijkstra算法類似。該算法名稱以創始人之一、1978年圖靈獎獲得者、斯坦福大學計算機科學系教授羅伯特·弗洛伊德命名。

簡單的來說,算法的主要思想是動態規劃(dp),而求最短路徑需要不斷鬆弛(熟悉spfa算法的可能熟悉鬆弛)。

而算法的具體思想爲:

  1. 鄰接矩陣dist儲存路徑,同時最終狀態代表點點的最短路徑。如果沒有直接相連的兩點那麼默認爲一個很大的值(不要溢出)!而自己的長度爲0.
  2. 第1個到第n個點依次加入圖中。每個點加入進行試探是否有路徑長度被更改。
  3. 而上述試探具體方法爲遍歷圖中每一個點(i,j雙重循環),判斷每一個點對距離是否因爲加入的點而發生最小距離變化。如果發生改變,那麼兩點(i,j)距離就更改。
  4. 重複上述直到最後插點試探完成。

其中第三步的狀態轉移方程爲:

  • dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j])
    其中dp[x][y]的意思可以理解爲x到y的最短路徑。所以dp[i][k]的意思可以理解爲i到k的最短路徑dp[k][j]的意思可以理解爲k到j的最短路徑.

咱們圖解一個案例:
在這裏插入圖片描述
默認的最短長度初始爲鄰接矩陣初始狀態

  • 加入第一個節點1,大家可以發現,由於1的加入,使得本來不連通的2,3點對和2,4點對變得聯通,並且加入1後距離爲當前最小。(可以很直觀加入5之後2,4,更短但是還沒加入)。爲了更好的描述其實此時的直接聯通點多了兩條。(2,3)和(2,4).我們在dp中不管這個結果是通過前面那些步驟來的,但是在這個狀態,這兩點的最短距離就算它!
    在這裏插入圖片描述
  • 同時你可以發現加入1其中也使得3,1,4這樣聯通,但是 3,1,4聯通的話距離爲9遠遠大於本來的(3,4)爲2,所以不進行更新。
  • 咱們繼續加入第二個節點。在加入的初始態爲:
    在這裏插入圖片描述
  • 進行遍歷插入看看是否更新節點
    在這裏插入圖片描述
    實際上這個時候圖中的連線就比較多了。當然這些連線都是代表當前的最短路徑。 這也和我們的需求貼合,我們最終要的是所有節點的最短路徑。每個節點最終都應該有6條指向不同節點的邊! 表示鄰接矩陣的最終結果。

至於算法的模擬兩部核心已經告訴大家了,大家可以自行模擬剩下的。

程序實現

而對於程序而言,這個插入的過程相當簡單。核心代碼只有四行!
代碼如下

public class floyd {
	static int max = 66666;// 別Intege.max 兩個相加越界爲負
	public static void main(String[] args) {
		int dist[][] = {
				{ 0, 2, 3, 6, max, max }, 
				{ 2, 0, max, max,4, 6 }, 
				{ 3, max, 0, 2, max, max },
				{ 6, max, 2, 0, 1, 3 }, 
				{ max, 4, max, 1, 0, max }, 
				{ max, 6, max, 3, max, 0 } };// 地圖
		// 6個
		for (int k = 0; k < 6; k++)// 加入滴k個節點
		{
			for (int i = 0; i < 6; i++)// 鬆弛I行
			{
				for (int j = 0; j < 6; j++)// 鬆弛i列
				{
					dist[i][j] = Math.min(dist[i][j], dist[i][k] + dist[k][j]);
				}
			}
		}
		// 輸出
		for (int i = 0; i < 6; i++) {
			System.out.print("節點"+(i+1)+" 的最短路徑");
			for (int j = 0; j < 6; j++) {
				System.out.print(dist[i][j]+" ");
			}
			System.out.println();
		}
	}
}

結果爲:
在這裏插入圖片描述
可以自行計算,圖和上篇的Dijkstra是一致的,大家可以自行比對,結果一致,說明咱麼的結果成功的。

當然,在你學習的過程中,可以在每加入一個節點插入完成後,打印鄰接矩陣的結果,看看前兩部和筆者的是否相同(有助於理解),如果相同,則說明正確!

你可能還會有疑惑,那咱麼就用一個局部性來演示一下,看其中AB最短距離變化情況祝你理解:
在這裏插入圖片描述

好啦,Floyd算法就介紹到這裏,如果對你有幫助,請動動小手點個贊吧!蟹蟹。
希望和各位共同進步!歡迎關注筆者公衆號:bigsai!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章