1 算法簡介
戴克斯特拉算法(英語:Dijkstra’s algorithm,又譯迪傑斯特拉算法)由荷蘭計算機科學家艾茲赫爾·戴克斯特拉在1956年提出。戴克斯特拉算法使用了廣度優先搜索解決賦權有向圖的單源最短路徑問題。該算法存在很多變體;戴克斯特拉的原始版本找到兩個頂點之間的最短路徑,但是更常見的變體固定了一個頂點作爲源節點然後找到該頂點到圖中所有其它節點的最短路徑,產生一個最短路徑樹。該算法常用於路由算法或者作爲其他圖算法的一個子模塊。
2 Dijkstra & 廣度優先搜索(BFS)
廣度優先搜索是尋找變數最少的路徑,Dijkstra算法是尋找權重和最小的路徑。當路徑權重一致時,兩種算法解決的問題是一致的。
3 算法實現
我們以實現下圖爲例實現Dijkstra算法:
在尋找最短路徑時,可以知道,最短路徑的子路徑也是最優路徑。爲了實現Dijkstra算法,我們需要三步,第一步是構造三個模塊,分別表示圖、到任一一節點的最短路徑、最短路徑對應的父節點。第二步是更新起點到各個點的最短路徑,包括終點。第三部是輸出與實現最短路徑查找。接下來我們分模塊來進行實現
3.1 構造三大模塊
首先是實現圖,對於有權重的圖,我們一般採用三元組來實現,如(A,F,1),在python中採用字典的嵌套來實現,上述圖可以表示爲
graph = {}
graph['S'] = {}
graph['S']['A'] = 6
graph['S']['B'] = 2
graph['A'] = {}
graph['A']['F'] = 1
graph['B'] = {}
graph['B']['A'] = 3
graph['B']['F'] = 5
graph['F'] = {}
其次是實現到任意節點的最短路徑,這裏使用cost表示路徑權值和,根據具體實例構建爲
# 從S點開始時,到三點依次爲6 2 和無窮(還不能到達)
infinity = float("inf")
costs = {}
costs['A'] = 6
costs['B'] = 2
costs['F'] = infinity
第三個模塊是構建最短路徑條件下的節點關係,使用字典表示,構建父子節點鍵值對,其中key表示子節點,value表示父節點
# 存儲父節點
parents = {}
parents['A'] = 'S'
parents['B'] = 'S'
parents['F'] = None
3.2 構建查詢最小權值點
在算法中,進行下一步建立在前一步是最短路徑的基礎上,因此在向下一個node邁進時,需要查詢當前權值和最小的點,代碼實現如下
def find_lowest_cost_node(costs): # 在costs中找到現在權值和最小的node
lowest_cost = float("inf") # 先將最小cost的點設爲無且權值最大
lowest_cost_node = None
for node in costs: # 遍歷現在每一個cost中的node,如果小則替換掉
cost = costs[node]
if cost < lowest_cost and node not in processed:
lowest_cost = cost
lowest_cost_node = node
return lowest_cost_node # 返回找出最小cost的node點
3.3 路徑更新
爲了實現路徑更新,需要對最短路徑進行處理。處理方法是當前最短路徑的node想其鄰居node進行移動,並且找到在所有鄰居node中權值和最小的node,同時對三大模塊中存儲的數據進行更新。具體代碼實現如下:
# 建立已處理的node
processed = []
node = find_lowest_cost_node(costs)
while node is not None:
cost = costs[node] # cost表示當前權值和最小的node的權值
neighbors = graph[node] # 找到node點可以到達的點的列表
for n in neighbors.keys():
new_cost = cost + neighbors[n]
if costs[n] > new_cost: # 如果改節點前往鄰居更近
costs[n] = new_cost # 修改鄰居節點的權值和
parents[n] = node # 將該鄰居的父節點設置爲當前節點
processed.append(node) # 處理後標記
node = find_lowest_cost_node(costs)
3.4 最短路徑查詢
經過上述處理,我們知道在costs字典中存儲着從起點到各個node的最短路徑權值,parents中存儲着最優路徑下父子節點的關係,因此需要對parents進行處理,確保能夠輸入一個node作爲參數時,能夠輸出最短路徑移動過程。具體代碼實現如下
def find_path(node):
path = [] # 建立路徑生成的空列表
while node in parents.keys(): # 確定node在parent中則將其加入path
path.append(node)
node = parents[node]
path.append('S') # 加入起點
for i in range(len(path)):
if i != len(path)-1:
print("{} --> ".format(path[-i-1]),end = '')
else:
print(path[0])
4 整體代碼測試
下面的代碼是對上面具體用例的彙總,並進行了簡單的測試。完整Dijkstra算法代碼爲:
graph = {}
graph['S'] = {}
graph['S']['A'] = 6
graph['S']['B'] = 2
graph['A'] = {}
graph['A']['F'] = 1
graph['B'] = {}
graph['B']['A'] = 3
graph['B']['F'] = 5
graph['F'] = {}
# 從S點開始時,到三點依次爲6 2 和無窮(還不能到達)
infinity = float("inf")
costs = {}
costs['A'] = 6
costs['B'] = 2
costs['F'] = infinity
# 存儲父節點
parents = {}
parents['A'] = 'S'
parents['B'] = 'S'
parents['F'] = None
def find_lowest_cost_node(costs): # 在costs中找到現在權值和最小的node
lowest_cost = float("inf") # 先將最小cost的點設爲無且權值最大
lowest_cost_node = None
for node in costs: # 遍歷現在每一個cost中的node,如果小則替換掉
cost = costs[node]
if cost < lowest_cost and node not in processed:
lowest_cost = cost
lowest_cost_node = node
return lowest_cost_node # 返回找出最小cost的node點
# 建立已處理的node
processed = []
node = find_lowest_cost_node(costs)
while node is not None:
cost = costs[node] # cost表示當前權值和最小的node的權值
neighbors = graph[node] # 找到node點可以到達的點的列表
for n in neighbors.keys():
new_cost = cost + neighbors[n]
if costs[n] > new_cost: # 如果改節點前往鄰居更近
costs[n] = new_cost # 修改鄰居節點的權值和
parents[n] = node # 將該鄰居的父節點設置爲當前節點
processed.append(node) # 處理後標記
node = find_lowest_cost_node(costs)
def find_path(node):
path = [] # 建立路徑生成的空列表
while node in parents.keys(): # 確定node在parent中則將其加入path
path.append(node)
node = parents[node]
path.append('S') # 加入起點
for i in range(len(path)):
if i != len(path)-1:
print("{} --> ".format(path[-i-1]),end = '')
else:
print(path[0])
print(costs)
find_path('F')
輸出結果爲:
{'A': 5, 'B': 2, 'F': 6}
S --> B --> A --> F