日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Floyd算法的动态规划本质

發布時間:2024/10/6 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Floyd算法的动态规划本质 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Floyd–Warshall(簡稱Floyd算法)是一種著名的解決任意兩點間的最短路徑(All Paris Shortest Paths,APSP)的算法。從表面上粗看,Floyd算法是一個非常簡單的三重循環,而且純粹的Floyd算法的循環體內的語句也十分簡潔。我認為,正是由于“Floyd算法是一種動態規劃(Dynamic Programming)算法”的本質,才導致了Floyd算法如此精妙。

因此,這里我將從Floyd算法的狀態定義、動態轉移方程以及滾動數組等重要方面,來簡單剖析一下圖論中這一重要的基于動態規劃的算法——Floyd算法。

在動態規劃算法中,處于首要位置、且也是核心理念之一的就是狀態的定義。在這里,把d[k][i][j]定義成:

“只能使用第1號到第k號點作為中間媒介時,點i到點j之間的最短路徑長度。”

圖中共有n個點,標號從1開始到n。因此,在這里,k可以認為是動態規劃算法在進行時的一種層次,或者稱為“松弛操作”。

  • d[1][i][j]表示只使用1號點作為中間媒介時,點i到點j之間的最短路徑長度;
  • d[2][i][j]表示使用1號點到2號點中的所有點作為中間媒介時,點i到點j之間的最短路徑長度;
  • d[n-1][i][j]表示使用1號點到(n-1)號點中的所有點作為中間媒介時,點i到點j之間的最短路徑長度d[n][i][j]表示使用1號到n號點時,點i到點j之間的最短路徑長度。

有了狀態的定義之后,就可以根據動態規劃思想來構建動態轉移方程。

?????? 動態轉移的基本思想可以認為是建立起某一狀態之前狀態的一種轉移表示按照前面的定義,d[k][i][j]是一種使用1號到k號點的狀態,可以想辦法把這個狀態通過動態轉移,規約到使用1號到(k-1)號的狀態,即d[k-1][i][j]。對于d[k][i][j](即使用1號到k號點中的所有點作為中間媒介時,i和j之間的最短路徑),可以分為兩種情況:

  • i到j的最短路不經過k;
  • i到j的最短路經過了k;
  • 具體來說:

  • 不經過點k的最短路情況下,d[k][i][j]=d[k-1][i][j]。
  • 經過點k的最短路情況下,d[k][i][j]=d[k-1][i][k]+d[k-1][k][j]。
  • 因此,綜合上述兩種情況,便可以得到Floyd算法的動態轉移方程:

    d[k][i][j] = min(d[k-1][i][j], d[k-1][i][k]+d[k-1][k][j])(k,i,j∈[1,n])

    最后,d[n][i][j]就是所要求的圖中所有的兩點之間的最短路徑的長度。在這里,需要注意上述動態轉移方程的初始(邊界)條件,即d[0][i][j]=w(i, j),也就是說在不使用任何點的情況下(“松弛操作”的最初),兩點之間最短路徑的長度就是兩點之間邊的權值(若兩點之間沒有邊,則權值為INF;且我比較偏向在Floyd算法中把圖用鄰接矩陣的數據結構來表示,因為便于操作)。當然,還有d[i][i]=0(i∈[1,n])。

    這樣我們就可以編寫出最為初步的Floyd算法代碼:

    void floyd_original() {for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)d[0][i][j]=graph[i][j];for(int k=1;k<=n;k++)for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)d[k][i][j]=min(d[k-1][i][j],d[k-1][i][k]+d[k-1][k][j]); }

    幾乎所有介紹動態規劃中最為著名的“0/1背包”問題的算法書籍中,都會進一步介紹利用滾動數組的技巧來進一步減少算法的空間復雜度,使得0/1背包只需要使用一維數組就可以求得最優解。而在各種資料中,最為常見的Floyd算法也都是用了二維數組來表示狀態。那么,在Floyd算法中,是如何運用滾動數組的呢?

    再次觀察動態轉移方程?d[k][i][j] = min(d[k-1][i][j], d[k-1][i][k]+d[k-1][k][j]),可以發現每一個第k階段的狀態(d[k][i][j]),所依賴的都是前一階段(即第k-1階段)的狀態(如d[k-1][i][j],d[k-1][i][k]和d[k-1][k][j])。

    上圖描述了在前面最初的Floyd算法中,計算狀態d[k][i][j]時,d[k-1][ ][ ]和d[k][ ][ ]這兩個二維數組的情況

  • d[k-1][][]表示第k-1階段時,圖中兩點之間最短路徑長度的二維矩陣;
  • d[k][][]表示第k階段時,圖中兩點之間最短路徑長度的二維矩陣;
  • 紅色帶有箭頭的有向線段指示了規劃方向。灰色表示已經算過的數組元素。白色代表還未算過的元素。由于d[k-1][][]和d[k][][]是兩個相互獨立的二維數組,因此利用d[k-1][i][j],d[k-1][i][k]和d[k-1][k][j](皆處于上方的二維數組中)來計算d[k][i][j]時沒有任何問題。

    那如何利用一個二維數組來實現滾動數組,以減小空間復雜度呢?

    上圖是使用滾動數組,在第k階段,計算d[i][j]時的情況。此時,由于使用d[][]這個二維數組作為滾動數組,在各個階段的計算中被重復使用,因此數組中表示階段的那一維也被取消了。

    在這圖中,白色的格子,代表最新被計算過的元素(即第k階段的新值),而灰色的格子中的元素值,其實保存的還是上一階段(即第k-1階段)的舊值。因此,在新的d[i][j]還未被計算出來時,d[i][j]中保存的值其實就對應之前沒有用滾動數組時d[k-1][i][j]的值。此時,動態轉移方程在隱藏掉階段索引后就變為:

    d[i][j] = min(d[i][j], d[i][k]+d[k][j])(k,i,j∈[1,n])

    賦值號左側d[i][j]就是我們要計算的第k階段是i和j之間的最短路徑長度。在這里,需要確保賦值號右側的d[i][j], d[i][k]和d[k][j]的值是上一階段(k-1階段)的值。前面已經分析過了,在新的d[i][j]算出之前,d[i][j]元素保留的值的確就是上一階段的舊值。

    但至于d[i][k]和d[k][j]呢?我們無法確定這兩個元素是落在白色區域(新值),還是灰色區域(舊值)。

    好在有這樣一條重要的性質:dp[k-1][i][k] 和 dp[k-1][k][j] 是不會在第k階段改變大小的。也就是說,凡是和k節點相連的邊,在第k階段的值都不會變。如何簡單證明呢?

    我們可以把j=k代入之前的d[k][i][j]=min(d[k-1][i][j], d[k-1][i][k]+d[k-1][k][j])方程中,即:

    d[k][i][k]

    = min(d[k-1][i][k], d[k-1][i][k]+d[k-1][k][k])

    = min(d[k-1][i][k], d[k-1][i][k]+0)

    = d[k-1][i][k]

    也就是說在第k-1階段和第k階段,點i和點k之間的最短路徑長度是不變的。相同可以證明,在這兩個階段中,點k和點j之間的的最短路徑長度也是不變的。

    因此,對于使用滾動數組的轉移方程d[i][j] = min(d[i][j], d[i][k]+d[k][j])來說,賦值號右側的d[i][j], d[i][k]和d[k][j]的值都是上一階段(k-1階段)的值,可以放心地被用來計算第k階段時d[i][j]的值。

    利用滾動數組改寫后的Floyd算法代碼如下:

    void floyd() {for(int k = 1; k <= n; k++)for(int i = 1; i <= n; i++)for(int j = 1; j <= n; j++)d[i][j] = min(d[i][j], d[i][k] + d[k][j]); }

    ?

    總結

    以上是生活随笔為你收集整理的Floyd算法的动态规划本质的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。