Floyd 算法

Floyd 算法

簡介

  • Floyd 算法又稱為插點法,是一種利用動態(tài)規(guī)劃的思想尋找給定的加權(quán)圖中多源點之間最短路徑的算法默责,與 Dijkstra 算法類似。

  • 該算法名稱以創(chuàng)始人之一鲫懒、1978 年圖靈獎獲得者遭居、斯坦福大學(xué)計算機科學(xué)系教授羅伯特 · 弗洛伊德命名。

核心思路

路徑矩陣

  • 通過一個圖的權(quán)值矩陣求出它的每兩點間的最短路徑矩陣玛追。
  • 從圖的帶權(quán)鄰接矩陣 A=[a(i,j)] n×n 開始税课,遞歸地進行 n 次更新,即由矩陣 D(0)=A豹缀,按一個公式伯复,構(gòu)造出矩陣 D(1);又用同樣地公式由 D(1) 構(gòu)造出 D(2)邢笙;……啸如;最后又用同樣的公式由 D(n-1) 構(gòu)造出矩陣 D(n)。矩陣 D(n) 的 i 行 j 列元素便是 i 號頂點到 j 號頂點的最短路徑長度氮惯,稱 D(n) 為圖的距離矩陣叮雳,同時還可引入一個后繼節(jié)點矩陣 path 來記錄兩點間的最短路徑。
  • 采用松弛技術(shù)(松弛操作)妇汗,對在 i 和 j 之間的所有其他點進行一次松弛帘不。所以時間復(fù)雜度為 O(n^3)。

狀態(tài)轉(zhuǎn)移方程

  • path[i,j]:=min{path[i,k]+path[k,j],path[i,j]}

算法描述

  1. 從任意一條單邊路徑開始杨箭。所有兩點之間的距離是邊的權(quán)寞焙,如果兩點之間沒有邊相連,則權(quán)為無窮大互婿。

  2. 對于每一對頂點 u 和 v捣郊,看看是否存在一個頂點 w (一般稱為中間點)使得從 u 到 w 再到 v 比已知的路徑更短。如果是慈参,更新它(專業(yè)術(shù)語為:松弛)呛牲。

  3. 遍歷直到結(jié)束,這時候存儲圖的數(shù)據(jù)結(jié)構(gòu)就得到了多源最短路徑驮配。

優(yōu)缺點分析

  • Floyd 算法適用于 APSP(All Pairs Shortest Paths娘扩,多源最短路徑)着茸,是一種動態(tài)規(guī)劃算法,稠密圖效果最佳琐旁,邊權(quán)可正可負涮阔。此算法簡單有效,由于三重循環(huán)結(jié)構(gòu)緊湊旋膳,對于稠密圖澎语,效率要高于執(zhí)行 | V | 次 Dijkstra 算法,也要高于執(zhí)行 | V | 次 SPFA 算法验懊。

    稠密圖定義:邊的條數(shù) | E | 接近 | V|2擅羞,稱為稠密圖(dense graph)

  • 優(yōu)點:容易理解,可以算出任意兩個節(jié)點之間的最短距離义图,代碼編寫簡單减俏。

  • 缺點:時間復(fù)雜度比較高,不適合計算大量數(shù)據(jù)娃承。時間復(fù)雜度 O(n^3),空間復(fù)雜度 O(n^2)。

Python 代碼實現(xiàn)

  • 代碼:

      import copy
      M=1000000
      def Floyd(G):
          n=len(G)
          path=copy.deepcopy(G)
          for k in range(0,n):
              for i in range(0,n):
                  for j in range(0,n):
                      print("Comparing path[%s][%s] and {path[%s][%s]+path[%s][%s]}"%(i,j,i,k,k,j))
                      print("Former path[%s][%s] = %s"%(i,j,path[i][j]))
                      path[i][j]=min(path[i][j],path[i][k]+path[k][j])
                      print("Present path[%s][%s] = %s\n"%(i,j,path[i][j]))
          return path
    
      if __name__=='__main__':
          G=[
              [0,30,15,M,M,M],
              [5,0,M,M,20,30],
              [M,10,0,M,M,15],
              [M,M,M,0,M,M],
              [M,M,M,10,0,M],
              [M,M,M,30,10,0]
              ]
          print("---------------Floyd----------------")
          path=Floyd(G)
          print("Graph = ")
          for i in range(0,len(G)):
              print (path[i])
    

代碼詳解

  • 引入 copy 怕篷,定義無窮值历筝,在 Python 里還有種寫法: M = float('inf')

  • 定義 Floyd 函數(shù),參數(shù)為圖的鄰接矩陣 G廊谓,在里面首先利用 copy 函數(shù)復(fù)制一份梳猪,不復(fù)制的話,會導(dǎo)致兩個變量指向同一個地址(鄰接矩陣)

  • 三重 for 循環(huán)蒸痹,很容易理解

  • if __name__=='__main__'

    不理解的復(fù)制這段代碼春弥,百度谷歌

測試

  • 還是拿我的另一篇文章 Dijkstra 算法 中的測試用例

  • 鄰接矩陣:

          G=[
              [0,30,15,M,M,M],
              [5,0,M,M,20,30],
              [M,10,0,M,M,15],
              [M,M,M,0,M,M],
              [M,M,M,10,0,M],
              [M,M,M,30,10,0]
              ]
    
  • 表示的圖如下:

    test
  • 輸出:

    floydtest

小結(jié)

  • 從上面的輸出結(jié)果可以看到 G 矩陣已經(jīng)被更新了

  • 有些走不通的路徑現(xiàn)在可以走通了,因為原來是鄰接矩陣:兩個頂點有鄰接邊叠荠,鄰接矩陣對應(yīng)點才有值匿沛;現(xiàn)在是整個網(wǎng)絡(luò),頂點 i 到頂點 j 如果可達榛鼎,那么 G[i][j] 就有值逃呼。

  • 但是,我們是得到了兩個點的最小 cost者娱,最短路徑要怎么得到呢蜘渣?可以再借助一個 path 矩陣,它記錄了中間點的信息肺然,我們可以在最后根據(jù)更新后的 G 矩陣回溯 path 矩陣得到最短路徑。

全局最短路徑的數(shù)據(jù)結(jié)構(gòu)

ShortestPath_dict = {
    0: {1: [0, 2, 1], 2: [0, 2], 3: [0, 2, 5, 4, 3], 4: [0, 2, 5, 4], 5: [0, 2, 5]},
    1: {0: [1, 0], 2: [1, 0, 2], 3: [1, 4, 3], 4: [1, 4], 5: [1, 5]}, 2: {0: [2, 1, 0],
    1: [2, 1], 3: [2, 5, 4, 3], 4: [2, 5, 4], 5: [2, 5]},
    3: {0: [], 1: [], 2: [], 4: [], 5: []},
    4: {0: [], 1: [], 2: [], 3: [4, 3], 5: []},
    5: {0: [], 1: [], 2: [], 3: [5, 4, 3], 4: [5, 4]}
    }
  • 這個數(shù)據(jù)結(jié)構(gòu)利用了字典與列表腿准,其中每兩個頂點的最短路徑用列表表示际起,如頂點 0 到頂點 1 :0: {1: [0, 2, 1]拾碌,其中的[0, 2, 1]就是最短路徑。

利用 Floyd 得到全局最短路徑

  • 代碼:

      # ------------------函數(shù)-------------------
      def back_path(path,i,j,shortestPath):            #遞歸回溯
          print ("path[%s][%s] = "%(i,j),path[i][j])
          if -1 != path[i][j]:
              shortestPath = back_path(path,i,path[i][j],shortestPath)
              shortestPath = back_path(path,path[i][j],j,shortestPath)
          if j not in shortestPath:
              shortestPath.append(j)
          return shortestPath
    
      def getShortestPath(graph,path,i,j):
          shortestPath = []
          if graph[i][j] == float('inf') or i == j:
              print("頂點%s 不能到達 頂點%s街望!"%(i,j))
              return shortestPath
          elif path[i][j] == -1:
              shortestPath.append(i)
              shortestPath.append(j)
          else :
              shortestPath.append(i)
              shortestPath = back_path(path,i,j,shortestPath)
          print("頂點%s 到 頂點%s 的路徑為:"%(i,j),shortestPath)
          return shortestPath
    
      def getAllShortestPath(graph,path):
          print("------正在生成全局最短路徑------")
          ShortestPath_dict = {}
          for i in range(N):
              ShortestPath_dict[i] = {}
              for j in range(N):
                  print("嘗試生成頂點%s到頂點%s的最短路徑..."%(i,j))
                  if i !=j :
                      shortestPath = getShortestPath(graph,path,i,j)
                      ShortestPath_dict[i][j] = shortestPath
                  print("--------------------------------")
          return ShortestPath_dict
    
      # ----------------------定義--------------------
      M=float('inf')      #無窮大
      graph = [
              [0,30,15,M,M,M],
              [5,0,M,M,20,30],
              [M,10,0,M,M,15],
              [M,M,M,0,M,M],
              [M,M,M,10,0,M],
              [M,M,M,30,10,0]
              ]
      N = len(graph)
      path = []
      for i in range(N):
          path.append([])
          for j in range(N):
              path[i].append(-1)
    
      print ("Original Graph:\n",graph)
      # -----------------Floyd Algorithm----------------
      for k in range(N):
          for i in range(N):
              for j in range(N):
                  if graph[i][j] > graph[i][k] + graph[k][j]:
                      graph[i][j] = graph[i][k] + graph[k][j]
                      path[i][j] = k
    
      print ("Shortest Graph:\n",graph)
      print ("Path:\n",path)
    
      print("ShortestPath =\n",getAllShortestPath(graph,path))
    

測試2

  • 鄰接矩陣如上所示

  • 表示的圖也如上所示

  • 截圖

    test3

    last

代碼詳解2

  • 在三重循環(huán)階段校翔,與前文第一種做法不同的是:每次還加入了path[i][j] = k,這樣每次松弛灾前,都會被記錄在 path 矩陣中

  • path 矩陣完成后防症,如測試2中的截圖所示

  • getAllShortestPath 函數(shù)

    • 構(gòu)造字典 ShortestPath_dict
    • 兩兩遍歷圖中各點,循環(huán)調(diào)用 getShortestPath 函數(shù)
  • getShortestPath 函數(shù)

    • 如果兩個頂點不可達或者這兩個頂點為同一個頂點哎甲,那么直接說明不可達蔫敲,且返回值為空列表:[]
    • 如果兩個頂點不需要松弛就可以得到最短路徑,那么在 path 矩陣中對應(yīng)點的值就為初始值 -1炭玫,這代表了這兩個頂點鄰接奈嘿,在 getShortestPath 函數(shù)中是需要判斷的
    • 如果兩個頂點需要松弛才能得到最短路徑,那么在 path 矩陣中對應(yīng)點的值就不為 -1吞加,那么需要遞歸調(diào)用 back_path 函數(shù)
  • back_path 函數(shù)

    • 之前說了裙犹,path 矩陣中記錄的是每次松弛操作時的中間點,所以在函數(shù)中前后兩次調(diào)用本身
  • 舉例說明

    • 以測試2的截圖為例衔憨,嘗試頂點0到頂點3的最短路徑:
      • 進入 getShortestPath(graph,path,i,j) 叶圃,調(diào)用 back_path(path,0,3,[0])
      • 首先調(diào)用 back_path(path,0,3,[0]) ,發(fā)現(xiàn) path[0][3]=5践图,說明需要頂點5的中轉(zhuǎn)
      • 于是調(diào)用 back_path(path,0,5,[0]) 掺冠,發(fā)現(xiàn) path[0][5]=2,說明需要頂點2的中轉(zhuǎn)
      • 繼續(xù)調(diào)用 back_path(path,0,2,[0]) 平项,發(fā)現(xiàn)到頭了赫舒,頂點0和頂點2直接鄰接,于是不再遞歸下去了闽瓢,添加頂點2到最短路徑 shortestPath接癌,此時 [0,2]
      • 返回上層,調(diào)用 back_path(path,2,5,[0,2]) 扣讼,發(fā)現(xiàn)到頭了缺猛,頂點2和頂點5直接鄰接,于是不再遞歸下去了荔燎,添加頂點5到最短路徑 shortestPath,此時 [0,2,5]
      • 返回上層销钝,調(diào)用 back_path(path,5,3,[0,2,5])有咨,發(fā)現(xiàn)需要頂點4的中轉(zhuǎn)
      • 于是調(diào)用 back_path(path,5,4,[0,2,5]),發(fā)現(xiàn)到頭了蒸健,不再遞歸座享,添加頂點4到最短距離婉商,此時 [0,2,5,4]
      • 返回上層,調(diào)用 back_path(path,4,3,[0,2,5,4])渣叛,發(fā)現(xiàn)到頭了丈秩,不再遞歸,添加頂點3到最短距離淳衙,此時 [0,2,5,4,3] 已經(jīng)是最短距離了
      • 注意:因為只要調(diào)用 back_path 就會添加頂點進入最短路徑 shortestPath蘑秽,為了避免重復(fù),加入了 if 判斷箫攀,直接過濾掉重復(fù)的頂點肠牲,因為最短路徑不可能繞圈子。

源碼地址:https://github.com/edisonleolhl/DataStructure-Algorithm/blob/master/Graph/ShortestPath/floyd.py

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末匠童,一起剝皮案震驚了整個濱河市埂材,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌汤求,老刑警劉巖俏险,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異扬绪,居然都是意外死亡竖独,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進店門挤牛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來莹痢,“玉大人,你說我怎么就攤上這事墓赴【荷牛” “怎么了?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵诫硕,是天一觀的道長坦辟。 經(jīng)常有香客問我,道長章办,這世上最難降的妖魔是什么锉走? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮藕届,結(jié)果婚禮上挪蹭,老公的妹妹穿的比我還像新娘。我一直安慰自己休偶,他們只是感情好梁厉,可當我...
    茶點故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著踏兜,像睡著了一般词顾。 火紅的嫁衣襯著肌膚如雪只冻。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天计技,我揣著相機與錄音,去河邊找鬼山橄。 笑死垮媒,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的航棱。 我是一名探鬼主播睡雇,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼饮醇!你這毒婦竟也來了它抱?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤朴艰,失蹤者是張志新(化名)和其女友劉穎观蓄,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體祠墅,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡侮穿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了毁嗦。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片亲茅。...
    茶點故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖狗准,靈堂內(nèi)的尸體忽然破棺而出克锣,到底是詐尸還是另有隱情,我是刑警寧澤腔长,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布袭祟,位于F島的核電站,受9級特大地震影響饼酿,放射性物質(zhì)發(fā)生泄漏榕酒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一故俐、第九天 我趴在偏房一處隱蔽的房頂上張望想鹰。 院中可真熱鬧,春花似錦药版、人聲如沸辑舷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽何缓。三九已至肢础,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間碌廓,已是汗流浹背传轰。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留谷婆,地道東北人慨蛙。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像纪挎,于是被迫代替她去往敵國和親期贫。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,901評論 2 355

推薦閱讀更多精彩內(nèi)容