一、Floyd算法本质

  首先,关于Floyd算法:

  Floyd-Warshall算法是一种在具有正或负边缘权重(但没有负周期)的加权图中找到最短路径的算法。算法的单个执行将找到所有顶点对之间的最短路径的长度(加权)。

  通俗一点说,Floyd就是可以用于求解多源汇最短路径的算法,也就是求连通图中任意两点间的最短路径,当然,如果不连通,它返回的就是无穷大(初始化为无穷大)。Floyd可以处理负权,但无法处理有负权环的图。

  接下去进入正题:

  众所周知,Floyd算法本质其实是动态规划。它其实是由三维数组的DP优化而来。

  我们用数组dis[i,j,k]表示从点i到点j,以前k个点作为中转点的最短路径长度。

  为了实现状态转移,我们把当前dis[i,j,k]的所有状态的集合划分为两类,一类是经过k点的,一类是不经过k点的。对于前者,显然dis[i,j,k]=dis[i,j,k-1];对于后者,我们可以得到dis[i,j,k]=dis[i,k,k-1]+dis[k,j,k-1],也就是i到k的最短路径长度加上k到j的最短路径长度。于是我们便可以得到状态转移方程:

  dis[i,j,k] = min(dis[i,j,k-1],dis[i,k,k-1]+dis[k,j,k-1]

  边界条件:dis[i,j,0] = w[i,j],即i与j之间的直接边的权值,若不存在则为正无穷;还有dis[i,i,0]=0。

  代码如下:

  1. void floyd_original() {
  2. for(int k=;k<=n;k++) {
  3. for(int i=;i<=n;i++) {
  4. for(int j=;j<=n;j++) {
  5. dis[i][j][k]=min(dis[i][j][k-],dis[i][k][k-]+dis[k][j][k-]);
  6. }
  7. }
  8. }
  9. }

  类比前面背包问题的优化方式,我们发现对于每一层k,它的状态计算只与第k-1层的状态有关,那么我们便可以省略这一维。因为省略之后,在计算第k层的dis[i,j]时,我们所需的dis[i,k]和dis[k,j]还是上一层的。

  这一点用一个式子便可证明:

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

  dis[k,j]同理即可得证。

  于是我们便可得到最普遍的二维数组的状态转移方程:

  dis[i,j] = min(dis[i,j],dis[i,k]+dis[k,j])

  从三维变成二维确实降低了空间开销,但是我们也可以发现时间复杂度是不变的,仍然是O(n³)。

二、Floyd算法变形解决有边数限制的最短路问题

  我们用三维数组d[i,j,e]表示点i到点j,经过e条边的最短路径长度。

  我们假设经过的倒数第二个点是k,那么我们很容易就可以得到状态转移方程:

  d[i,j,e]=min{d[i,k,e-1]+w[k,j]} k∈[1,n]

  代码如下:

  1. for(int e=;e<=n;e++)
  2. for(int k=;k<=n;k++)
  3. for(int i=;i<=n;i++)
  4. for(int j=;j<=n;j++)
  5. d[i][j][e]=min(d[i][j][e],d[i,k,e-]+w[k][j]);

  但是这样处理的时间复杂度高达O(n4),于是我们自然会想到要做一些优化。

  我们为了达到明显的指数级别的优化效果,我们选择二进制优化。

  假设限制的边数为s时,我们把第三维e表示成2e条边。那么我们预处理时只需要将2e<s的所有符合条件的e枚举完即可。然后便可以用若干个2的整数次幂的和表示出s。

  我们再用数组f[i,j,t]来表示状态,其中t表示边数为前t个2的整数次幂的和,那么我们就可以得到状态转移方程:

  f[i,j,t] = min{f[i,k,t-1]+d[k,j,s(t)]}

  其中s(t)表示将s进行2的指数幂分解后,所得的所有的2的幂中的第t个2的幂。k是i到j的最短路径中间经过的一个点,将路径中所有的边划分为前20+21+…+2t-1条与后2t条。

  那么我们就只需在最外层枚举k即可。这个算法的时间复杂度就可以降低到O(n3logn)。

算法思路:

  先预处理出d数组,d[i,j,e]表示从顶点i到顶点j,经过2k条边的最短路长度。找到中介点k,将路径边数分成两半。

  状态转移方程如下:

  d[i,j,e]=min(d[i,j,e],d[i,k,e-1]+d[k,j,e-1]}

  然后处理f数组。

  代码如下:

  1. #include <iostream>
  2. #include <cstdio>
  3. #include <cstring>
  4. #include <algorithm>
  5. #include <cmath>
  6. using namespace std;
  7. const int N = 1e3+;
  8. const int K = log2(N);
  9. int n,m,s;
  10. int d[N][N][K],f[N][N][K]; //使用邻接矩阵存储图
  11.  
  12. void floyd(int s) //有边数限制的最短路问题
  13. {
  14. memset(f,0x3f,sizeof f);
  15. int z[K],max_e=log2(s); //s为所限制的边数
  16.  
  17. memset(z,,sizeof z);
  18.  
  19. //先处理出s的2的指数次幂的分解,用z数组存储
  20. //如 p=2^1+2^4+2^5,则z[1]=1,z[2]=4,z[3]=5
  21.  
  22. int cnt=,sum=;
  23. while(s)
  24. {
  25. if(s&)z[++cnt]=sum;
  26. sum++;
  27. s>>=;
  28. }
  29.  
  30. //处理d数组,d[i][j][k]表示从i到j经过2^k条边的最短路长度
  31. //复杂度 n^3*logs
  32. for(int e=;e<=max_e;e++)
  33. for(int i=;i<=n;i++)d[i][i][e]=; //处理d的边界
  34. for(int e=;e<=max_e;e++)
  35. {
  36. for(int k=;k<=n;k++)
  37. for(int i=;i<=n;i++)
  38. for(int j=;j<=n;j++)
  39. {
  40. //状态转移方程如下
  41. d[i][j][e]=min(d[i][j][e],d[i][k][e-]+d[k][j][e-]);
  42. //找到中介点k,将2^e条边的最短路分成两半,分别是2^(e-1)条。
  43. }
  44. }
  45.  
  46. for(int t=;t<=cnt;t++)
  47. for(int i=;i<=n;i++)f[i][i][t]=; //处理f的边界
  48. for(int t=;t<=cnt;t++)
  49. {
  50. for(int k=;k<=n;k++)
  51. for(int i=;i<=n;i++)
  52. for(int j=;j<=n;j++)
  53. {
  54. //状态转移方程
  55. f[i][j][t]=min(f[i][j][t],f[i][k][t-]+d[k][j][z[t]]);
  56. }
  57. }
  58.  
  59. printf("%d\n",f[][n][cnt]);
  60. }
  61.  
  62. int main()
  63. {
  64. scanf("%d%d%d",&n,&m,&s);
  65.  
  66. memset(d,0x3f,sizeof d); // d数组的初始化
  67.  
  68. for(int i=;i<=m;i++)
  69. {
  70. int u,v,w;
  71. scanf("%d%d%d",&u,&v,&w);
  72. d[u][v][]=d[v][u][]=w; // d数组的赋值
  73. }
  74.  
  75. floyd(s);
  76. return ;
  77. }

关于算法适用范围:

  个人认为,由于时间复杂度如此感人,相比于同样用于处理“有边数限制的最短路问题”的Bellman-Ford算法的最坏情况,也就是遇到完全图时,O(nm)变成O(n3),也是过犹不及。

  但是,当题目毒瘤到一定程度的时候,当出题人变态到一种境界的时候,边数极其之多,Bellman-Ford算法所用以存储图的边集数组所需的空间开销极大,超过限制时,就是这个算法大展拳脚的时候了。

  大家对这个算法有兴趣的话可以去看一下这道题:POJ 3613

图论——Floyd算法拓展及其动规本质的更多相关文章

  1. [图论]Floyd 算法小结

    Floyd 算法小结  By Wine93 2013.11 1. Floyd算法简介 Floyd算法利用动态规划思想可以求出任意2点间的最短路径,时间复杂度为O(n^3),对于稠密图, 效率要高于执行 ...

  2. 图论·Floyd算法·HDU2544&1874 (伪)2066

    在看到1874的题时,第一反应是用上一篇的并查集方法,后来查了一下是要用Floyd做,所以就去查Floyd算法的资料. 即插点法,是一种用于寻找给定的加权图中顶点间最短路径的算法. 核心代码:  ma ...

  3. 【uva 10048】Audiophobia(图论--Floyd算法)

    题意:有一个N点M边的无向带权图,边权表示路径上的噪声值.有Q个询问,输出 x,y 两点间的最大噪声值最小的路径的该值.(N≤100,M≤1000,Q≤10000) 解法:N值小,且问多对点之间的路径 ...

  4. Floyd 算法的动态规划本质

    [转载自:http://www.cnblogs.com/chenying99/p/3932877.html] Floyd–Warshall(简称Floyd算法)是一种著名的解决任意两点间的最短路径(A ...

  5. 探求Floyd算法的动态规划本质(转)

    ---恢复内容开始--- Floyd–Warshall(简称Floyd算法)是一种著名的解决任意两点间的最短路径(All Paris Shortest Paths,APSP)的算法.从表面上粗看,Fl ...

  6. 探求Floyd算法的动态规划本质

    Floyd–Warshall(简称Floyd算法)是一种著名的解决任意两点间的最短路径(All Paris Shortest Paths,APSP)的算法.从表面上粗看,Floyd算法是一个非常简单的 ...

  7. 图论(floyd算法):NOI2007 社交网络

    [NOI2007] 社交网络 ★★   输入文件:network1.in   输出文件:network1.out   简单对比 时间限制:1 s   内存限制:128 MB [问题描述] 在社交网络( ...

  8. 图论之最短路径floyd算法

    Floyd算法是图论中经典的多源最短路径算法,即求任意两点之间的最短路径. 它可采用动态规划思想,因为它满足最优子结构性质,即最短路径序列的子序列也是最短路径. 举例说明最优子结构性质,上图中1号到5 ...

  9. LCS(最长公共子序列)动规算法正确性证明

    今天在看代码源文件求diff的原理的时候看到了LCS算法.这个算法应该不陌生,动规的经典算法.具体算法做啥了我就不说了,不知道的可以直接看<算法导论>动态规划那一章.既然看到了就想回忆下, ...

随机推荐

  1. 图解Java继承内存分配

    图解Java继承内存分配   继承的基本概念: (1)Java不支持多继承,也就是说子类至多只能有一个父类. (2)子类继承了其父类中不是私有的成员变量和成员方法,作为自己的成员变量和方法. (3)子 ...

  2. 在Pytorch上使用稀疏矩阵

    在Pytorch上使用稀疏矩阵 最近在写一个NLP的小项目,用到了Pytorch做神经网络模型.但是众所周知NLP的一个特点就是特征矩阵是稀疏矩阵,当时处理稀疏矩阵用的是scipy.sparse,现在 ...

  3. arcgis 服务网页打开需要输入用户名和密码问题解决

    解决方法: 在站点manager中,检查服务的安全性,确认是否是公共.如果不是,设置为公共,面向任何人:如果服务已经被设置为面向公共,那么先设置为私有,面向所选用户,然后再设置为公共,面向任何人 如果 ...

  4. phpspider爬虫框架的使用

    这几天使用PHP的爬虫框架爬取了一些数据,发现还是挺方便的,先上爬虫框架的文档 phpspider框架文档 使用方法其实在文档中写的很清楚而且在demo中也有使用示例,这里放下我自己的代码做个笔记 & ...

  5. 10 TCP限流技术

    TCP限流是因为让接收方充分接受完消息,保证数据安全,不会丢失 一.窗口机制介绍 发送端和接收端都拥有一个窗口,当发送端发送数据时,落进窗口的数据被发送,当接受端接受数据时,落进接收端窗口的数据将会被 ...

  6. Linux软链接创建及删除

    1.创建软链接 具体用法是:ln  -s   [源文件]   [软链接文件]. [root@localhost folder]# pwd /tmp/folder [root@localhost fol ...

  7. 【Git】六、分支管理&冲突解决

    上一节讲了如何和远端的仓库协同工作,这一节介绍一下分支 ---------------------------- 提要 //创建一个分支dev $ git branch dev //切换到dev分支 ...

  8. 《OpenCV图像处理编程实例》

    <OpenCV图像处理编程实例>例程复现 随书代码下载:http://www.broadview.com.cn/28573 总结+遇到的issue解决: 第一章 初识OpenCV 1.VS ...

  9. python异步编程 (转载)

    Python Async/Await入门指南   转自:https://zhuanlan.zhihu.com/p/27258289 本文将会讲述Python 3.5之后出现的async/await的使 ...

  10. 06_Hive分桶机制及其作用

    1.Clustered By 对于每一个表(table)或者分区, Hive可以进一步组织成桶,也就是说桶是更为细粒度的数据范围划分. Hive也是针对某一列进行桶的组织.Hive采用对列值哈希,然后 ...