floyd算法和动态规划

楔子

long long ago就已经知道了Floyd算法,关键代码就4行,也容易记住,上上周又看到了Floyd,都说是动态规划,所以特意去学了一圈动态规划,今天终于又回到了它

状态方程:

d[k][i][j]定义:“只能使用第1号到第k号点作为中间媒介时,点i到点j之间的最短路径长度。”
在动态规划算法中,处于首要位置、且也是核心理念之一的就是状态的定义
这个大家喜欢把它叫做“松弛操作”,也就是relax
对于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[0][i][j]=w(i, j),d[k][0][j]=0,d[k][i][0]=0

原始的实现如下:

# 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])
import numpy as np
def floyd_original(graph):
    vertex_num = len(graph)
    
    list = np.full((vertex_num+1,vertex_num+1,vertex_num+1),np.inf)
    list[:,0,:] = 0
    list[:,:,0] = 0
    
    for i in range(1,vertex_num+1):
        for j in range(1,vertex_num+1):
            list[0,i,j] = graph[i-1,j-1]
    
    for k in range(1,vertex_num+1):
        for i in range(1,vertex_num+1):
            for j in range(1,vertex_num+1):
                list[k,i,j] = min(list[k-1,i,j],list[k-1,i,k]+list[k-1,k,j])
                
    return list[vertex_num,1:,1:]

观察DP数组,可以做空间优化,类似于背包问题使用滚动数组优化:

1、d[k][i][j]只与d[k-1][i][j], d[k-1][i][k]+d[k-1][k][j]有关,也就是只和d[k-1],那么我们就可以只用一个二维数组代替三维的数组;

2、然后看我们是否需要注意遍历这个二维数组的顺序(例如01背包问题需要逆序遍历才能保证用的数据是上一层的,完全背包需要顺序遍历才能保证用的数据是更新后的这一层的),假如采取顺序的话,d[k-1][i][j]这个没问题,d[k-1][i][k]+d[k-1][k][j]这个k永远是小于或者等于i的,所以d[k-1][i][k]+d[k-1][k][j]取的是应该是更新后的数据,他们在已更新的区域里,那不是不能使用滚动数组了?但是事实已经证明可以这么用,后来发现一个解释:

3、有这样一条重要的性质,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]的值。
有没有很坑? 绕了一圈又回到不熟悉的东西上去了
仔细看一遍上述的公式就懂了。
所以我们可以使用滚动数组来优化空间。

实现如下:

import numpy as np
def floyd(graph):
    vertex_num = len(graph)   
    list = np.zeros((vertex_num+1,vertex_num+1))
    
    for i in range(1,vertex_num+1):
        for j in range(1,vertex_num+1):
            list[i,j] = graph[i-1,j-1]
    
    for k in range(1,vertex_num+1):
        for i in range(1,vertex_num+1):
            for j in range(1,vertex_num+1):
                list[i,j] = min(list[i,j],list[i,k]+list[k,j])
                
    return list[1:,1:]

运行结果

#%%
graph = np.full((7,7),np.inf)
graph[0,:3] = [0,1,2]
graph[1,:4] = [1,0,3,4]
graph[2,:4] = [2,3,0,6]
graph[3,1:5] = [4,6,0,7]
graph[4,3:6] = [7,0,9]
graph[5,4:7] = [9,0,10]
graph[6,5:7] = [10,0]

    

print floyd_original(graph)

print floyd(graph)


[[ 0.  1.  2.  5. 12. 21. 31.]
 [ 1.  0.  3.  4. 11. 20. 30.]
 [ 2.  3.  0.  6. 13. 22. 32.]
 [ 5.  4.  6.  0.  7. 16. 26.]
 [12. 11. 13.  7.  0.  9. 19.]
 [21. 20. 22. 16.  9.  0. 10.]
 [31. 30. 32. 26. 19. 10.  0.]]
 
[[ 0.  1.  2.  5. 12. 21. 31.]
 [ 1.  0.  3.  4. 11. 20. 30.]
 [ 2.  3.  0.  6. 13. 22. 32.]
 [ 5.  4.  6.  0.  7. 16. 26.]
 [12. 11. 13.  7.  0.  9. 19.]
 [21. 20. 22. 16.  9.  0. 10.]
 [31. 30. 32. 26. 19. 10.  0.]]


猜你喜欢

转载自blog.csdn.net/weixin_40759186/article/details/84305509