Floyd算法:
状态:
d[k][i][j]定义:“只能使用第1号到第k号点作为中间媒介时,点i到点j之间的最短路径长度。”
动态转移方程:
d [ k ] [ i ] [ j ] = m i n ( d [ k − 1 ] [ i ] [ j ] , d [ k − 1 ] [ i ] [ k ] + d [ k − 1 ] [ k ] [ j ] ) ( k , i , j ∈ [ 1 , n ] ) 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[k][i][j]=min(d[k−1][i][j],d[k−1][i][k]+d[k−1][k][j])(k,i,j∈[1,n])
Bellman-Ford 算法:
状态:
d[k][u]定义:从源点v出发最多经过不构成负权值回路的k条边到达终点u的最短路径的长度
动态转移方程:
d i s t [ k ] [ u ] = m i n ( d i s t [ k − 1 ] [ u ] , m i n ( d i s t [ k − 1 ] [ j ] + E d g e [ j ] [ u ] ) ) , j = 0 , 1 , . . . , n − 1 ; j ! = u dist[k][u] = min(dist[k-1][u] ,min(dist[k-1][j]+Edge[j][u])),j = 0,1,…,n-1;j!=u dist[k][u]=min(dist[k−1][u],min(dist[k−1][j]+Edge[j][u])),j=0,1,...,n−1;j!=u
从上面可以看出这两种算法,对于子问题考虑前者考虑的是基于点的中转,后者考虑是基于边的中转,基于边的中转需要考虑不形成回路,求最小的话,非负数情况下,肯定是没有回路的,存在负权回路,就没有最小值,需要检测是否有负权回路。
Bellman-Ford 算法基于动态规划的原始实现:
#%%
# dist[k][u] = min{ dist[k-1][u] ,min{dist[k-1][j]+Edge[j][u]} },j = 0,1,...,n-1;j!=u
import numpy as np
def Bellman_Ford_original(graph,start):
vertex_num = len(graph)
edge_num_ = vertex_num-1
list = np.full((edge_num_+1,vertex_num+1),np.inf)
# for i in range(1,vertex_num+1):
# list[1,i] = graph[start,i-1]
list[:,start+1] =0
for k in range(1,edge_num_+1):
for u in range(1,vertex_num+1):
result = list[k-1,u]
for j in range(1,vertex_num+1):
if graph[j-1,u-1]>0 and graph[j-1,u-1]<10000 and result > list[k-1,j] + graph[j-1,u-1]:
result = list[k-1,j] + graph[j-1,u-1]
list[k,u] =result
return list[k,1:]
使用滚动数组加入空间优化:list[k-1,u],list[k-1,j],假如使用一维数组的话,list[k-1,u]是未更新的主句没问题,list[k-1,j]就不好说了,既有可能是更新过的数据,也有可能是未更新的数据,理论上应该不能用滚动数组优化,但是:只要数据可以被更新,就代表v到u的距离可以边更小,是不影响整体数据向更小方向推进的。
使用一维数组优化版本,加入了追踪解实现:
def Bellman_Ford(graph,start):
vertex_num = len(graph)
edge_num_ = vertex_num-1
list = np.full((vertex_num+1),np.inf)
list[start+1] = 0
path =[-1] * vertex_num
for i in range(vertex_num):
if graph[start,i] < 10000:
path[i] = start
path[start] = None
# for i in range(1,vertex_num+1):
# list[i] = graph[start,i-1]
for k in range(1,edge_num_+1):
flag = True
for u in range(1,vertex_num+1):
for j in range(1,vertex_num+1):
w = graph[j-1,u-1]
if w>0 and w <10000 and list[u] > list[j] + w :
list[u] = list[j] + w
path[u-1] = j-1
flag = False
# 提前退出,并检查是否有负回路
if flag == True:
for u in range(1,vertex_num+1):
for j in range(1,vertex_num+1):
if list[u] > list[j] + graph[j-1,u-1] :
print "there is a - cycle"
break
break
return list[1:],path
运行结果:
graph = np.full((7,7),np.inf)
graph[0,:3] = [0,-1,2]
graph[1,:4] = [-1,0,3,4]
graph[2,:5] = [2,3,0,5,6]
graph[3,1:6] = [4,5,0,7,8]
graph[4,2:6] = [6,7,0,9]
graph[5,3:7] = [8,9,0,10]
graph[6,5:7] = [10,0]
print Bellman_Ford_original(graph,6)
value,path = Bellman_Ford(graph,6)
print value
print path
[25. 22. 23. 18. 19. 10. 0.]
there is a - cycle
[25. 22. 23. 18. 19. 10. 0.]
[2, 3, 3, 5, 5, 6, None]
另外一种理解,从松弛的角度:
第一,初始化所有点。每一个点保存一个值,表示从原点到达这个点的距离,将原点的值设为0,其它的点的值设为无穷大(表示不可达)。
第二,进行循环,循环下标为从1到n-1(n等于图中点的个数)。在循环内部,遍历所有的边,进行松弛计算。
第三,遍历途中所有的边(edge(u,v)),判断是否存在这样情况:
d(v)>d(u) + w(u,v)
则返回false,表示途中存在从源点可达的权为负的回路。
之所以需要第三部分的原因,是因为,如果存在从源点可达的权为负的回路。则应为无法收敛而导致不能求出最短路径。