1.Floyd-Warshall 算法
给定一张图,在o(n3)时间内求出任意两点间的最小距离,并可以在求解过程中保存路径
2.Floyd-Warshall 算法概念
这是一个动态规划的算法。
将顶点编号,假设依次为0,1,2…n-1,现在假设DP[i][j][k]表示从i出发,结束于j的满足经过结点的编号至多为k的最短路径,由此性质易知,在易知DP[i][j][k]时,若要求DP[i][j][k+1],有两种情况要考虑:
- DP[i][j][k+1]所表征的路径经过结点k+1,此时DP[i][j][k+1] = DP[i][k+1][k] + DP[k+1][j][k]
- DP[i][j][k+1]所表征的路径不经过结点k+1,此时DP[i][j][k+1] = DP[i][j][k],不用更新表项
属于哪种情况只需进行一次比较选择较小的即可,当第n-1轮循环结束,表项中的值DP[i][j]就代表了顶点i , j之间的最短距离,由算法的描述易知,时间复杂度必然是O(N3),但是空间复杂度可以通过复用DP数组减少到O(N2),这是为什么呢?如何保存路径?
分析1:要证明DP数组只需两维,只需证明第k+1轮循环中DP数组前面被改动的部分不会被用到即可且用到的一定没有被改动即可。假设第i论循环中dp[i][j]及之前的dp数组项已经被计算出来,接下来的运算中dp[inext][jnext],需要计算上面两种情况下的值:
- 对于第1种情况,DP[inext][jnext][k+1] = DP[inext][k+1][k] + DP[k+1][jnext][k],对于DP[inext][k+1][k],如果它在本轮循环被更新,那么它实际上可以被标识为DP[inext][k+1][k+1],这就是一个矛盾,第k+1个点已经作为端点存在,却又说k+1个顶点可能在路径中存在。所以它不可能在本轮被更新,即实际上它还是DP[inext][k+1][k],DP[k+1][jnext][k]的分析相同,见下面的分析2.
- 对第2种情况,DP[inext][jnext][k+1] = DP[inext][jnext][k] , 要么inext>i,要么inext==i&&jnext>j,即DP[inext][jnext]之前肯定未被更新,不存在问题。
分析2:一个令人迷惑的问题就是在第k+1轮循环计算DP[i][k+1](DP[k+1][j]的分析相同),按照上面两种情况分类 ,二者相等, 可以知道的确是不用更新的
- 对第一种情况:DP[i][k+1][k+1] = DP[i][k+1][k] + DP[k+1][k+1][k] = DP[i][k+1][k]
- 对第二种情况:DP[i][k+1][k+1] = DP[i][k+1][k]
分析3:初始状况的分析,初始状况相当于不经过任何结点,对于任意两个顶点 i , j ,自然有
- 若 i , j 之间有边相连,则 DP[i][j] = cost(i,j)
- 反之 ,DP[i][j] = INF
分析4 : 如何记录路径,设path[i][j]表示 i 到 j 的最短路径中 i 的后继顶点,初始情况下,若i ,j 之间有边相连,path[i][j] = j ,否则,path[i][j] = –1,在不断收敛的过程中,若当前最短路径有变化,path[i][j] = path[i][k+1]
3.代码
头文件:
/* areslipan@163.com filename : Floyd_Warshall.h */ #ifndef _FLOYD_WARSHALL_ #define _FLOYD_WARSHALL_ #include "graph.h" using namespace std; void Floyd_Warshall(Graph_Matrix & g,Graph_Matrix &path,Graph_Matrix &dp); void Floyd_Warshall_Path(Graph_Matrix &path, int start, int end); #endif |
实现文件:
/* areslipan@163.com filename : Floyd_Warshall.cpp*/#include "Floyd_Warshall.h"using namespace std;void Floyd_Warshall(Graph_Matrix & g,Graph_Matrix &path,Graph_Matrix &dp){ //可以先用Bellman-Ford算法检查一下是否有负值回路int numNodes=g.size();//图用矩阵表示,因此为了方便起见path和dp表也按照图的方式存放 path=g; dp=g;for(int i=0;i<numNodes;++i) { for(int j=0;j<numNodes;++j) { if(g[i][j]>=MYINF)path[i][j] = -1 ;//-1 表示不经过任何结点else path[i][j] = j; dp[i][j]=g[i][j]; } }for(int k=0;k<numNodes;++k)for(int i=0;i<numNodes;++i)for(int j=0;j<numNodes;++j) { if(dp[i][k]<MYINF && dp[k][j]<MYINF && dp[i][k]+dp[k][j]<dp[i][j]) { dp[i][j] = dp[i][k] + dp[k][j]; path[i][j] = path[i][k];//path[i][j]存储i到j的路线上i的后继结点 } }}void Floyd_Warshall_Path(Graph_Matrix &path, int start, int end){cout<<"从"<<start<<"到"<< end <<"的最短路径 : "; while(start!=end) { cout<<start<<" "; start=path[start][end];if(start==-1) { cout<<"no route!!"<<endl;return; } } cout<<end<<endl;} |
测试文件:
/* areslipan@163.com filename : testFloydWarshall.cpp */ #include "Floyd_Warshall.h" using namespace std; int main() { vector |
测试用例:
1000 表示无穷大
0 1 4 1000 1000 1000 1 0 2 7 5 1000 4 2 0 1000 1 1000 1000 7 1000 0 3 2 1000 5 1 3 0 6 1000 1000 1000 2 6 0 |
算法结果: