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了。其实算不上什么大改,也肯定有人早已经尝试过,或者这种方法很多人一下就想到了,并且不会绕那么大弯。

不过,我认为学习算法的过程就是理解算法背后思想的过程,写多少道题并不能说一定有提升,但是对于每个题目背后的逻辑,如果能融会贯通,遇到相似的题目的时候也能举一反三了。

猜你喜欢

转载自blog.csdn.net/qq_45492531/article/details/104506749