1. 程式人生 > >Bellman-Ford 演算法 和 動態規劃

Bellman-Ford 演算法 和 動態規劃

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])

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

從上面可以看出這兩種演算法,對於子問題考慮前者考慮的是基於點的中轉,後者考慮是基於邊的中轉,基於邊的中轉需要考慮不形成迴路,求最小的話,非負數情況下,肯定是沒有迴路的,存在負權迴路,就沒有最小值,需要檢測是否有負權迴路。

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,表示途中存在從源點可達的權為負的迴路。
之所以需要第三部分的原因,是因為,如果存在從源點可達的權為負的迴路。則應為無法收斂而導致不能求出最短路徑。