(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之间的最短路径),可以分为两种情况:(1)i到j的最短路不经过k;(2)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算法代码:
1
2
3
4
5
6
7
8
9
10
11
12
|
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算法代码如下:
1
2
3
4
5
6
|
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算法的的确确是一种典型的动态规划算法;理解Floyd算法,也可以帮助我们进一步理解动态规划思想。
(floyd)佛洛伊德算法的更多相关文章
- SGU 455 Sequence analysis(Cycle detection,floyd判圈算法)
题目链接:http://acm.sgu.ru/problem.php?contest=0&problem=455 Due to the slow 'mod' and 'div' operati ...
- Floyd最短路算法
Floyd最短路算法 ----转自啊哈磊[坐在马桶上看算法]算法6:只有五行的Floyd最短路算法 暑假,小哼准备去一些城市旅游.有些城市之间有公路,有些城市之间则没有,如下图.为了节省经费以及方便计 ...
- UVA 11549 CALCULATOR CONUNDRUM(Floyd判圈算法)
CALCULATOR CONUNDRUM Alice got a hold of an old calculator that can display n digits. She was bore ...
- UVA 11549 Calculator Conundrum (Floyd判圈算法)
题意:有个老式计算器,每次只能记住一个数字的前n位.现在输入一个整数k,然后反复平方,一直做下去,能得到的最大数是多少.例如,n=1,k=6,那么一次显示:6,3,9,1... 思路:这个题一定会出现 ...
- leetcode202(Floyd判圈算法(龟兔赛跑算法))
Write an algorithm to determine if a number is "happy". 写出一个算法确定一个数是不是快乐数. A happy number ...
- 【啊哈!算法】算法6:只有五行的Floyd最短路算法
暑假,小哼准备去一些城市旅游.有些城市之间有公路,有些城市之间则没有,如下图.为了节省经费以及方便计划旅程,小哼希望在出发之前知道任意两个城市之前的最短路程. 上图中有 ...
- Floyd判圈算法
Floyd判圈算法 leetcode 上 编号为202 的happy number 问题,有点意思.happy number 的定义为: A happy number is a number defi ...
- Codeforces Gym 101252D&&floyd判圈算法学习笔记
一句话题意:x0=1,xi+1=(Axi+xi%B)%C,如果x序列中存在最早的两个相同的元素,输出第二次出现的位置,若在2e7内无解则输出-1. 题解:都不到100天就AFO了才来学这floyd判圈 ...
- Floyd判断环算法总结
Floyd判断环算法 全名Floyd’s cycle detection Algorithm, 又叫龟兔赛跑算法(Floyd's Tortoise and Hare),常用于链表.数组转化成链表的题目 ...
随机推荐
- JFFS2 文件系统及新特性介绍
简介: JFFS2 是一个开放源码的项目(www.infradead.org). 它是在闪存上使用非常广泛的读/写文件系统,在嵌入式系统中被普遍的应用.这篇文章首先分析了在闪存上使用 JFFS2 的必 ...
- List转换成JSON对象
List转换成JSON对象 1.准备工作 导入jar包 (1)json-lib-2.3-jdk15.jar (2)commons-beanutils-1.8.0.jar (3)commons-coll ...
- 利用PowerDesigner15在win7系统下对MySQL 进行反向工程(一)
利用PowerDesigner15在win7系统下对MySQL 进行反向工程 1.首先,安装以下的驱动 2.找到"C:\Windows\System32" 3.双击"od ...
- java.sql.SQLException: ORA-01841
1.错误描述 body = (null) clientId = "DB719904-1E0C-35DC-725D-86ABCF2B6EEC" correlationId = &qu ...
- hihocoder Challenge 29 B.快速乘法
这题的题解和我写的有一拼,异常简洁,爆炸. 这题思路dp 表示的是讨论到第位,并比原数的前n位多了 显然j只能取0,1,毕竟2进制嘛 之后转移就好了,注意下面两个重要状态 #include <c ...
- freemarker中的round、floor和ceiling数字的舍入处理(十七)
1.简易说明 (1)round:四舍五入 (2)floor:向下取整 (3)ceiling:向上取整 2.举例说明 <#--freemarker中的round.floor和ceiling数字的舍 ...
- lvy打包到本地
图解
- ASP.NET VS2013 Office 转 PDF
本文适用于VS2013 项目中的Word转换为PDF.Excel转换为PDF.PPT转换为PDF 0.一种更加简单方便的方法 1.本页所用的方法在本机测试时基本不会出现问题,只是偶尔PPT转PDF失败 ...
- RobotFramework自动化测试框架-常用断言关键字
断言关键字 描述 Should Be Empty 判断是否为空,如果不为空,执行失败,示例: ${value} Set Variable Hello Should Be Empty ${value} ...
- ssm整合快速入门程序(一)
整合基础说明 spring 是一个开放源代码的设计层面框架,他解决的是业务逻辑层和其他各层的松耦合问题,因此它将面向接口的编程思想贯穿整个系统应用.Spring是于2003 年兴起的一个轻量级的Jav ...