Floyd算法的深入理解 嘗試魔改核心代碼

https://blog.csdn.net/qq_45492531/article/details/104452588
在我這個博客中,一次訓練的機會讓我接觸到了floyd算法。

對這個算法的思路理解之後,想追尋本源地挖掘一下這個算法背後的思想,以及算法設計者是怎麼往這個方向想的。

那這篇博客就打破傳統的floyd核心代碼的框架,對他魔改一下,並保證它的算法功能不會影響,可以的話,再優化一下。

重溫一遍算法的思路:

任意兩個路口之間,可能的情況無非是直接一步過去,要麼就中間經過若干路口間接到達。那麼如果i路口和j路口之間的若有一箇中轉路口k,i可以先到k再到j。則兩點之間的最短時間便是

dp[i][j] = min(dp[i][k]+dp[k][j],dp[i][j])。

然後k就像一箇中轉站一樣,所有與k位置有路相同的兩點都可以與之相連,並且使得這兩點通過k點聯通起來
外層循環控制1到n爲中轉站,內兩層遍歷一遍兩兩端點,每次i->j並以k作爲中轉的時候,都會取到這個狀態下的最優解,比如5->10可能是5->6->10爲最短路徑,這時候在k爲6時dp[5][10]就會更新成最優解,如果5到6之間有更多的中轉站也是同理。

那麼他的核心代碼就是

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

以上就是我們熟悉的floyd算法的核心部分

現在我們要有探索精神,爲啥k一定要放在最外層呢?我中轉的思路爲什麼一定是要每次兩兩端點都以1到n爲中轉遍歷呢?如果我換一下k循環的位置,會發生什麼?(此處艾特跟我一起討論的隊友)

試一下,我們把k循環放最裏層,會有什麼不同。

來一組數據
在這裏插入圖片描述
看起來沒問題,但是真的如此嗎?

不用說,這個代碼肯定是WA的,別問爲什麼我知道。

說明原因之前,先看下一組數據
在這裏插入圖片描述
這組數據足以說明問題
本該有的最短路徑是
1->3->4->2->5

而這個代碼告訴我們它直接走了1->3->5。
爲什麼會產生這種結果呢?
從結論入手的話,發現返回的仍然是dp[1][n],而這個dp[1][n]早在外層循環第一次遍歷的時候就完了,後面i從2到n根本不會對dp[1][n]產生任何影響,就會導致有路徑丟失的情況,那這個例子來說,當外層循環爲1時,也就是i爲1的時候,我們在路徑上能得到的最優解釋1->3->4,這一步在i=1的時候是可以完成的。但是4->2這一個過程完全還沒有遍歷到,因爲這要到i下標爲4的時候才能聯通這條路,更別說2->5這條路與前面聯通了。

這就是導致錯的原因。

好了,我們不僅要發現問題,還要解決問題。

既然i從1到n遍歷的時候會因爲後面的路還沒有打通而導致到要選擇的時候最短路還是沒有聯通的,那麼如果我讓i=1之前,就把路徑打通呢?

順着這個思想,我們試探性地,把外層循環倒序。

在這裏插入圖片描述
很好地解決了上面這個問題。

並且,這也是一份AC代碼

正如上面設想的一樣,在外層從n到1遍歷的時候,因爲1->n是最終狀態,

那麼我就要讓1到達每個中轉的路程也是最短的,同樣,中轉到n的距離也要是最短的

上面的數據就是個很簡單的例子,到達最優解的最後的一次選擇是1以2爲中轉,到達5的一個過程。

什麼?你說1到2還有路要走?(1->3->4->2),不好意思,3->4->2這條路早在i=3,k爲4到2的判斷中已經選好了。我們在i=1的位置只需要聯通以3爲中轉,選擇dp[1][3]+dp[3][2],這就是dp[1][2]的最優解啦。然後就是上面說的,再以2爲中轉到5,聯通最短路徑。

所以實踐及理論證明,魔改這個核心代碼也是可以的。

好了,那到這裏還能做些什麼優化呢?
當然是有,有些本就不能作爲中轉的點(壓根就沒路的),完全不用嘗試,你當前到中轉站的路都沒有,何必浪費一次判斷的時間呢?所以,我們先對路口彼此距離設爲一個無窮大量,在輸入的時候聯通的地方就會 有較小的數值,這時候我們只用判斷每個點與中轉站的距離是否比無窮大量小(是否有路),就可以剪枝啦。

for(int i=n;i>=1;i--)
    {
       for(int j=1;j<=n;j++)
        {
            for(int k=1;k<=n;k++)
            {
                 if(dp[i][k]<1e7&&dp[k][j]<1e7)
                 dp[i][j] = min(dp[i][j],dp[i][k]+dp[k][j]);
            }

        }
    }

注意:這裏所謂的“無窮大”量應按具體題目數據範圍而定,在運算過程中沒有數比他大就ok,不然會被誤入計算。

這就是我們“魔改”之後的floyd了。其實算不上什麼大改,也肯定有人早已經嘗試過,或者這種方法很多人一下就想到了,並且不會繞那麼大彎。

不過,我認爲學習算法的過程就是理解算法背後思想的過程,寫多少道題並不能說一定有提升,但是對於每個題目背後的邏輯,如果能融會貫通,遇到相似的題目的時候也能舉一反三了。

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