1. 程式人生 > >圖論之Dijkstra最短路徑演算法

圖論之Dijkstra最短路徑演算法

圖論中最有名的問題可能就屬最短路徑了。最短路徑問題要求解的是:如果從圖中某一頂點(稱為源點)到達另一頂點(稱為終點)的路徑可能不止一條,如何找到一條路徑,使得沿此路徑各邊上的權值總和(即從源點到終點的距離)達到最小,這條路徑稱為最短路徑(shortestpath)。最短路徑有很多特殊的情況,包括有向圖還是無向圖,有沒有負權邊等。這幾天我想介紹一下幾種常用的最短路徑演算法。今天先講情況最簡單(有向圖,沒有負權邊),但也是最有名Dijkstra演算法。

首先描述一下問題:給定一個有向圖G和源點v,求v0到G中某個頂點u的最短路徑。限定各邊上的權值大於或等於0。

演算法的基本思想很簡單:所有的頂點,按照它到源點v的距離,客觀上存在一個從小到大的順序,我們只要按照這個順序找下去,總有一步會找到目標頂點u,而此時的距離就是u到源點v的距離。

想法簡單,但關鍵是怎麼按照“客觀存在的大小順序”計算各點到源點v的距離呢?我們先簡單思考一下,按照距離v的遠近順序,v一定是排第一的,然後呢,排第二的一定是和v直接相連的點吧,否則總有一個點介於排第二的點和v之間,而它到v的距離肯定也比第二到v的距離近,那這個第二就有些名副其實了。比較麻煩的是,排第三的點在哪裡?利用遞迴的想法就會發現,排第三的點應該從那些和v直接相連,或者和第二名直接相連的點中去尋找,否則第三名就會名副其實了。總結一下,需要找第n個點時,只需要在那些和前n-1個點直接相連的點裡面找就行了。

Dijkstra演算法的具體實現方法為:

1.設定兩個頂點的集合T和S:

a) S中存放已找到最短路徑的頂點,初始時,集合S中只有一個頂點,即源點v0;

b) T中存放當前還未找到最短路徑的頂點;

2.在T集合中選取當前長度最短的一條最短路徑(v0,…,vk),從而將vk加入到頂點集合S中,並修改源點v0到T中各頂點的最短路徑長度;重複這一步驟,直到所有的頂點都加入到集合S中,演算法就結束了。

下面給個用Dijkstra計算最短路徑的例子

1)首先求出長度最短的一條最短路徑,即頂點0到頂點2的最短路徑,其長度為5,其實就是頂點0到其他各頂點的直接路徑中最短的路徑(v0→v2)。

2)頂點2的最短路徑求出來以後,頂點0到其他各頂點的最短路徑長度有可能要改變。例如從頂點0到頂點1的最短路徑長度由∞縮短為20,從頂點0到頂點5的最短路徑長度也由∞縮短為12。這樣長度次短的最短路徑長度就是在還未確定最終的最短路徑長度的頂點中選擇最小的,即頂點0到頂點5的最短路徑長度,為12,其路徑為(v0→v2→v5)。

3)頂點5的最短路徑求出來以後,頂點0到其他各頂點的最短路徑長度有可能要改變。例如從頂點0到頂點3的最短路徑長度由30縮短為22,從頂點0到頂點4的最短路徑長度也由∞縮短為30。這樣長度第三短的最短路徑長度就是在還未確定最終的最短路徑長度的頂點中選擇最小的,即頂點0到頂點1的最短路徑長度,為20,其路徑為(v0→v2→v1)。

4)此後再依次確定頂點0到頂點3的最短路徑(v0→v2→v5→v3),其長度為22;以及頂點0到頂點4的最短路徑(v0→v2→v1→v4),其長度為28。

# dists定義了圖,記錄著從從起點出發到其他頂點的距離
dist={1:{2:1,3:12},
      2:{3:9,4:3},
      3:{5:5},
      4:{3:4,5:13,6:15},
      5:{6:4},
      6:{6:0}}
cost={1:0,2:1,3:12,4:999,5:999,6:999}  # 由起點(結點1)到其餘頂點的最短距離,999代表無法到達
parents={1:None,2:1,3:2,4:2,5:3,6:5}   # parent代表到達這個結點的最短路徑的前一個結點
visited=[1]   # 起始結點預設已經訪問過

# 找到還沒有訪問的結點中路徑最短的一個結點
def findShorestNode(cost):
    minDist=999
    node=None
    for i in dist.keys():
        if (cost[i]<minDist)&(i not in visited):
            minDist=cost[i]
            node=i
    return node

# 更新最短路徑
node=findShorestNode(cost)
while node:
    for i in dist[node]:  # 所有node結點的鄰居結點
        newcost=cost[node]+dist[node][i]
        if newcost<cost[i]:
            parents[i]=node
            cost[i]=newcost
    visited.append(node)
    node=findShorestNode(cost)

# 打印出從1到6的最短路徑
parent=parents[6]
while parent:
    print(parent)
    parent=parents[parent]