Dijkstra in python

下面是一段由python實現的Dijkstra算法,一些地方的處理實在非常棒,相比於C,代碼的數量已經縮減到了60行,所以我想通過本文簡單的介紹一下這段代碼的細節之處,首先給出源程序:

from sys import argv
 
def dijkstra_score(G, shortest_distances, v, w):
    return shortest_distances[v] + G[v][w]
 
def dijkstra(G, source):
    unprocessed = set(G.keys()) # vertices whose shortest paths from source have not yet been calculated
    unprocessed.remove(source)
    shortest_distances = {source: 0}
 
    for i in xrange(len(G) - 1):
        # find a vertex with the next shortest path, i.e. minimal Dijkstra score
        m, closest_head = float('inf'), 0
        for tail in shortest_distances:
            for head in G[tail]:
                if head in unprocessed:
                    d = dijkstra_score(G, shortest_distances, tail, head)
                    if d < m:
                        m, closest_head = d, head
 
        unprocessed.remove(closest_head)
        shortest_distances[closest_head] = m
 
    # in case G is not fully connected
    for vertex in unprocessed:
        shortest_distances[vertex] = float('inf')
 
    return shortest_distances
 
def get_graph():
    filename = argv[1]
    graph = {}
    with open(filename) as g:
        for line in g:
            l = line.split()
            vertex = int(l.pop(0))
            graph[vertex] = {}
            for x in l:
                adj_vert, distance = map(int, x.split(","))
                graph[vertex][adj_vert] = distance
    print "Got graph. Ex: line 1:", graph[1]
    return graph
 
def main():
    G = get_graph()
    """ Input is adjacency list on vertices labelled 1 to n, including segment length.
    
    Example line of file:
    1   3,45    92,4
    
    This means that v. 1 is adjacent to v. 3 with edge length 45 and adjacent to v. 92 with edge length 4.
    """
    source = int(raw_input("Enter source vertex: "))
    destination_vertices = map(int, raw_input("List destination vertices:\n").split())
 
    distances = dijkstra(G, source)
 
    print "From vertex %d:" % source
    for vertex in destination_vertices:
        print "The distance to vertex %d is %d." % (vertex, distances[vertex])
 
if __name__ == '__main__':
    main()


使用方法:通過外部的文件定義圖的構造,每一行的格式爲:頂點   到達的頂點,距離    到達的頂點,距離


下面就從每一行值得注意的代碼進行分析:

1、圖的構造

def get_graph():
    filename = argv[1]
    graph = {}
    with open(filename) as g:
        for line in g:
            l = line.split()
            vertex = int(l.pop(0))
            graph[vertex] = {}
            for x in l:
                adj_vert, distance = map(int, x.split(","))
                graph[vertex][adj_vert] = distance
    print "Got graph. Ex: line 1:", graph[1]
    return graph

這裏的圖使用鄰接表的形式存儲,具體的實現採用的python當中的字典,一開始graph爲空,graph={}

然後打開存儲圖的文件,注意這裏採用了with語句,相當於try和finally的合體,open函數打開文件並將的返回值給了g。在文件g中的每一行使用split操作,去除空格,得到的l是一個列表,其中第一項就是原點,其餘的各項就是原點達到的其他的頂點及其距離。所以將每一個原點放進圖graph中作爲字典下標,而字典的值仍舊是一個字典,包括了兩項,第一項是原點到達的一個頂點,第二項是路徑的權值,最後將這兩項放入graph中對應的下標構成的字典中。

這樣,圖就算是構成了,得到的一個字典graph, 例如graph={1:{2,3}}表示的是頂點1到頂點2。


2、單源最短路徑

接下來就是通過另一個函數來構造出最短路徑了:

def dijkstra(G, source):
    unprocessed = set(G.keys()) # vertices whose shortest paths from source have not yet been calculated
    unprocessed.remove(source)
    shortest_distances = {source: 0}
 
    for i in xrange(len(G) - 1):
        # find a vertex with the next shortest path, i.e. minimal Dijkstra score
        m, closest_head = float('inf'), 0
        for tail in shortest_distances:
            for head in G[tail]:
                if head in unprocessed:
                    d = dijkstra_score(G, shortest_distances, tail, head)
                    if d < m:
                        m, closest_head = d, head
 
        unprocessed.remove(closest_head)
        shortest_distances[closest_head] = m
 
    # in case G is not fully connected
    for vertex in unprocessed:
        shortest_distances[vertex] = float('inf')
 
    return shortest_distances

首先,unprocessed保存了圖G中所有頂點的集合,用以表示還沒有加入到路徑中的頂點,初始化時就是全部的頂點,然後,通過傳入函數的source確定開始的頂點,並將該頂點從unprocessed中移除。而記錄最短路徑的方式則通過shortest_distance這個字典,初始化將自己加入,距離爲0。

接下來就是按照Dijkstra算法的步驟一步步進行了:對每一個新加入的頂點找到和這個頂點相鄰的邊,更新每個頂點的最短距離,這裏的實現方式就是通過一個大循環i執行len(G)-1次將每一個頂點都進行處理,每一次處理的開始,將m初始化爲無窮大,將closest_head初始化爲0,注意,m將會被用來存儲最短的距離,而closest_head將會被用來存儲最短距離的頂點編號。這裏,可以將已經處理好的頂點想象成一個相連的圖,而下一個加入到這個圖中的頂點就是從原點到剩餘頂點距離最短的那一個,具體實現是通過遍歷shortest_distance處理完成的頂點,這個字典中每一項都記錄了從原點到那個頂點的最短路徑,然後圖中剩下的沒有處理的並且相連的節點,通過dijkstra_score這個函數計算從原點到達那個頂點的距離,將其最小值保存在m中,於是,經過所有的頂點的遍歷,找到距離最小的那個點,將其放在shortest_distance中,那麼這個頂點就處理完了,接下來就是去處理其他剩餘的頂點了。

算法同時也考慮了加入沒有連通的情況下的距離,將其設置爲無窮大,當然,這裏所做的一切都假定所有邊的權值爲非負,因爲假如存在負數的權值,那麼最短距離可能不存在。





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章