概要
- 可以通過對每個結點運行一次上一章中的最短路徑算法,來解決所有結點對的最短路徑問題,但效率較低,本章考慮的是用新的算法更高效的解決此問題
- 本章使用鄰接矩陣來表示圖,邊的權重也存儲在一個矩陣 W 中,算法的輸出存儲在矩陣 D 中,還有一個精妙的前驅結點矩陣用于存放路徑結果
- 前驅結點矩陣的位置 ij 中存儲的是,由 i 到 j 的一條最短路徑中,j 的前驅結點
- 由前驅結點矩陣的第 i 行可以誘導出,由結點 i 出發的最短路徑樹
最短路徑和矩陣乘法
這一節是使用動態規劃的方法解決所有結點對的最短路徑問題,按動態規劃的設計方法,有四個步驟
步驟一:分析最優解結構
- 最優解必定是由子問題的最優解構成的,讓本章的問題符合這一性質的是引理 24.1,一條最短路徑的所有子路徑都是最短路徑
步驟二:所有結點對最短路徑的遞歸解
- 這一段的分析過程基本沒看明白,主要的目的是給出一個遞歸式,指明由子問題的解如何組成問題的解
- 公式給了兩個,第一個公式 25.2 指出了如何由子問題的解組成問題的解,第二個公式 25.3 結合路徑權重的場景具體分析,指出大于 n-1 條邊的權重矩陣中的權重值都等于 n-1 條邊的權重矩陣
步驟三:自底向上計算最短路徑權重
- 計算的算法并不出奇,三層 for 循環遍歷,利用公式 25.2 進行遞歸計算(遞歸應該算作第四次循環)
- 神奇的是這里計算的時候將公式 25.2 轉成了矩陣乘法(沒明白是如何轉換的,數學功力不夠),轉成矩陣乘法之后算法的表述就變得更簡單了,里面的三層 for 循環就是矩陣相乘的過程,外面的遞歸變成了不斷的與同一個矩陣做乘法
步驟三改進:重復平方
- 結合路徑權重的場景分析,我們只需要最終的權重矩陣,對計算的中間結果不感興趣,利用 25.3 給出的性質,遞歸計算改為平方計算,即中間結果的矩陣與自己相乘,這樣找出一個大于 n-1 階的結果即可
- 這個改進可以將遞歸中的 O(n) 優化到 O(lgn)
步驟四:構建最優解
- 書中沒有給出這一步驟,我思考就是利用概要中給出的方法,由前驅結點矩陣誘導出最短路徑樹
Floyd-Warshall 算法
- Floyd-Warshall 算法的運行時間為 V 的三次方
Floyd-Warshall 算法也是基于動態規劃的思想,但是是從另一個角度思考問題,下面的結構還是按照動態規劃的思路組織的
最短路徑結構(步驟一)
- Floyd-Warshall 算法考慮的是一條最短路徑上的中間結點(實話說從這句話里完全看不清算法的思路)
- 據我的理解,這里的問題和子問題分別是:
【問題】結點 i 到結點 j 的一條包含的中間結點全部取自 1 ~ k 號結點的最短路徑
【子問題】結點 i 到結點 j 的一條包含的中間結點為全部取自 1 ~ k-1 號結點的最短路徑
通俗一點講就是,i 到 j 的路徑的中間結點只能從 1 ~ k(或者 k-1)號結點中選擇,所形成的最短路徑
所有結點對最短路徑問題的一個遞歸解(步驟二)
- 給出的遞歸解是這樣的:關于問題 i 到 j 的最短路徑,k 階的 d 表示中間結點全部取自 1 ~ k 號結點的最短路徑的權重,遞歸解說明了如何從 k-1 階的 d 計算 k 階的 d,最后計算到 n 階的 d 就是在整個圖中 i 到 j 的最短路徑權重
自底向上計算最短路徑權重(步驟三)
- 這一段比較好理解,按照遞歸式進行計算即可,算法非常緊湊,運行時間為 n 的三次方
構建一條最短路徑(步驟四)
- 按照概述中的描述,構建最短路徑需要通過前驅矩陣進行誘導(生成最短路徑樹)
- 那么問題就變成了如何構建前驅矩陣,書中說明了兩種方法:
【方法一】由上述算法的計算結果 D 矩陣(由 n 階的 d 組成的矩陣),來構造前驅矩陣
【方法二】在計算 D 矩陣的同時計算前驅矩陣,說是同時其實關聯并不緊密,只是用到了 D 矩陣的中間結果,即 1 ~ n 階的 D 矩陣
有向圖的傳遞閉包
- 傳遞閉包是這樣定義的:定義帶權重的有向圖 G,如果 G 中的結點 i 到 j 存在可以到達的路徑,則(i,j)為 G 的傳遞閉包中的一條邊
- 可以利用 Floyd-Warshall 算法計算圖的傳遞閉包:給 G 的每條邊賦予權重 1,運行 Floyd-Warshall 算法,結果矩陣 D 中小于 n 的 d 值就代表一條閉包中的邊(或許書中的表述更易理解:如果存在一條 i 到 j 的路徑,則有 d(ij)< n)
- 還有另外一種方法,用邏輯或 & 邏輯與操作替代 Floyd-Warshall 算法中的 min 和 + 操作(至于為什么可以這樣替換我已經放棄思考了,應該是一個不太復雜的數學轉換),這樣改造后的算法運行會更快,因為邏輯或 & 邏輯與操作要比算法運算更快
- 我們可以利用 Floyd-Warshall 算法在 V 的三次方時間內計算出圖的傳遞閉包
用于稀疏圖的 Johnson 算法
- Johnson 算法可以在 O(V2lgV+VE) 內找到所有結點對的最短路徑,對于稀疏圖,E 比較小,則 Johnson 算法性能優于本章的前兩節的算法
- Johnson 算法的思路是對每個結點運行一次 Dijkstra 算法
- Dijkstra 算法要求權重都為非負值,所以這里可能需要(如果所有權重都為非負值則不需要)為每條邊重新賦予一個非負值權重,再對帶有新的權重的圖運行 Dijkstra 算法。其中重新賦予權重要求,新的圖的最短路徑結果集與原圖相同。
重新賦予權重來維持最短路徑
- 這一小段講的是如何計算一組與原圖的最短路徑相同的新權重
- 引理 25.1(重新賦予權重并不改變最短路徑)中的公式 25.9 給出了新權重的計算方法,通過選擇一個函數 h,利用公式 25.9 就能夠計算出一組最短路徑相同的新權重
通過重新賦值來生成非負權重
- 這一小段講的是如何選擇一個合適的函數 h,使得通過公式 25.9 計算出來的新權重都為非負值
- 選擇 h 的方法為:向圖 G 中添加一個新結點 s,定義 s 到其它所有結點的邊的權重都為 0,然后計算由 s 結點出發的單源最短路徑結果,函數 h(v) 定義為 s 到 v 的最短路徑權重
- 書中同時給出了這樣選擇函數 h 的正確性的證明,利用三角不等式,簡單明了
計算所有結點對之間的最短路徑
這一小段給出了 Johnson 算法的具體過程:
- 先插入新結點 s 生成新的圖 G‘ 并初始化
- 運行 Bellman-Ford 算法得到 s 的單元最短路徑結果,構造函數 h
- 由函數 h 計算新的權重(非負值),形成新的權重矩陣
- 對每個結點運行 Dijkstra 算法(使用新的權重矩陣),得到一個結果矩陣 D’
- 由 D‘ 根據公式 25.9 反向計算出原圖 G 的最短路徑結果矩陣 D
- 此外,如果還需要構建最短路徑,則可以在 D’ 的計算過程中保留前驅矩陣(將 Dijkstra 算法中生成前驅子圖記錄為前驅矩陣中的一行),這個前驅矩陣也可以作為原圖 G (舊的權重矩陣)的前驅矩陣