用图讲解狄克斯特拉(DiskStra)算法,python实现 。
最短路徑
在一個帶權(quán)圖中,頂點V0到圖中任意一個頂點Vi的一條路徑所經(jīng)過邊上的權(quán)值之和,定義為該路徑的帶權(quán)路徑長度,把帶權(quán)路徑最短的那條路徑稱為最短路徑。
如圖所示,求雙子峰到金門大橋的最短距離。
此類問題要用到狄克斯特拉(DiskStra)算法
使用狄克斯特拉(DiskStra)算法
圖中每個數(shù)字都是時間,為此找出從起點到終點用時最短的路徑。如果你使用廣度優(yōu)先搜索,廣度優(yōu)先搜索算法BFS講解以及python 實現(xiàn)
你將得到下面這條段數(shù)最少的路徑。
這條路徑耗費7分鐘,我們接下來找找還有沒有更短的路徑。
狄克斯特拉(DiskStra)算法 包含4個步驟:
第一步:你站在起點,可以走的點是A,B。到達A需要6分鐘,到達B需要2分鐘,其他節(jié)點不能到達,設(shè)為無窮。
第二步:因為起點到B最近,然后計算B前往各鄰居的距離
B到A的距離是3,我們找到一條前往A點的更短路徑5,從起點直接前往A點距離是6,則更新庫中起點到A的距離。
對于節(jié)點B的鄰居,如果找到前往它的更短路徑,就更新其開銷,在這里我們找到了:前往A點的更短路徑,時間由6分鐘縮短到了5分鐘;前往終點的更短路徑,時間由無窮大縮短到7分鐘。
第三步:重復(fù)
重復(fù)第一步,找出最短時間可以到達的點,前面對節(jié)點B執(zhí)行了第二步,除B點外,還有節(jié)點A
重讀第二步,更新節(jié)點A所有鄰居的開銷
你發(fā)現(xiàn)前往終點的時間為6分鐘。
對每個節(jié)點都應(yīng)用狄克斯特拉(DiskStra)算法,現(xiàn)在知道前往B節(jié)點2分鐘,A分鐘,終點6分鐘。
狄克斯特拉(DiskStra)算法適用于有向無環(huán)圖。
算例1實現(xiàn)
以圖為例
第一步:解決這個問題,需要準備三個散列表
第一個散列表表示圖,依次是父節(jié)點,子節(jié)點,權(quán)重。第一個散列表圖可以用散列表嵌套表示。
第二個散列表為初始時起點出發(fā)到各節(jié)點的距離,隨著算法進行需要不斷更新。
""" 第二個散列表 節(jié)點的開銷。節(jié)點的開銷表示起點出發(fā)到該節(jié)點需要多少時間""" infinity=float("inf")#無窮大 costs={} costs['a']=6 costs['b']=2 costs['fin']=infinity第三個散列表。左邊為子節(jié)點,右邊為父節(jié)點,用來表示路徑。隨著算法進行需要不斷更新,是針對出發(fā)點建立的
""" 第三個散列表 存儲 路徑 . parents['a']="start"表示節(jié)點a的上一節(jié)點是start,如果后面發(fā)現(xiàn)更短路徑,可以修改為parents['a']=b""" parents={} parents['a']="start" parents['b']="start" parents['fin']=None#出發(fā)點不能直接到達第二步:需要一個數(shù)組,存儲已經(jīng)處理過的節(jié)點
""" 需要一個數(shù)組,存儲已經(jīng)處理過的節(jié)點 """ processed=[]第三步:最小開銷節(jié)點函數(shù)
""" 開銷最小的函數(shù) """ def find_lowest_cost_node(costs):lowest_cost=float('inf')lowest_cost_node=Nonefor node in costs:#遍歷每個節(jié)點cost=costs[node]if cost<lowest_cost and node not in processed:#如果當前節(jié)點小于最小開銷,且沒有處理過lowest_cost_node=node #則將該節(jié)點設(shè)為最小開銷節(jié)點lowest_cost=cost#更新最小開銷return lowest_cost_node第四步:DiskStra算法
全部代碼
""" 第一個散列表 用于表示圖 """ graph={} #起點 到各鄰居的關(guān)系 graph["start"]={} graph["start"]["a"]=6 graph["start"]["b"]=2 #print(graph["start"].keys())#dict_keys(['a', 'b']) #節(jié)點a到各鄰居關(guān)系 graph["a"]={} graph["a"]["fin"]=1#節(jié)點b到各鄰居關(guān)系 graph["b"]={} graph["b"]["a"]=3 graph["b"]["fin"]=5#終點與各鄰居關(guān)系 graph["fin"]={}#終點沒有鄰居""" 第二個散列表 節(jié)點的開銷。節(jié)點的開銷表示起點出發(fā)到該節(jié)點需要多少時間,每個節(jié)點都要列出來""" infinity=float("inf")#無窮大 costs={} costs['a']=6 costs['b']=2 costs['fin']=infinity""" 第三個散列表 存儲 路徑 . parents['a']="start"表示節(jié)點a的上一節(jié)點是start,如果后面發(fā)現(xiàn)更短路徑,可以修改為parents['a']=b,每個節(jié)點都要列出來""" parents={} parents['a']="start" parents['b']="start" parents['fin']=None#出發(fā)點不能直接到達""" 需要一個數(shù)組,存儲已經(jīng)處理過的節(jié)點 """ processed=[]""" 開銷最小的函數(shù) """ def find_lowest_cost_node(costs):lowest_cost=float('inf')lowest_cost_node=Nonefor node in costs:#遍歷每個節(jié)點cost=costs[node]if cost<lowest_cost and node not in processed:#如果當前節(jié)點小于最小開銷,且沒有處理過lowest_cost_node=node #則將該節(jié)點設(shè)為最小開銷節(jié)點lowest_cost=cost#更新最小開銷return lowest_cost_node""" DiskStra算法""" node=find_lowest_cost_node(costs) #在未處理的節(jié)點中找到開銷最小的節(jié)點,即離出發(fā)點最近的點 while node is not None: #這個循環(huán)表示所有節(jié)點處理過才會結(jié)束cost=costs[node]neighbors=graph[node]for n in neighbors.keys():#遍歷當前節(jié)點的所有鄰居new_cost=cost+neighbors[n]if costs[n]>new_cost:#如果經(jīng)當前節(jié)點離該鄰居更近costs[n]=new_cost#就更新該節(jié)點的開銷parents[n]=node #同時將該鄰居的父節(jié)點設(shè)置為該節(jié)點processed.append(node)#將該節(jié)點標記為處理過node=find_lowest_cost_node(costs) #查找接下來要循環(huán)處理的點,并循環(huán)#打印結(jié)果 print('costs',costs) print('parents',parents)流程圖解
找到開銷最少的點
獲取該節(jié)點的開銷和鄰居
遍歷鄰居
每個節(jié)點都要開銷,開銷指從起點到該節(jié)點需要多長時間,在這里,將計算從起點到B再到A的開銷,而不是直接到A的開銷
和原始開銷進行對比
找到一條前往節(jié)點A的更短路徑,更新節(jié)點A的開銷 這條路徑由B到A,由此更新節(jié)點A的父節(jié)點現(xiàn)在回到for循環(huán),下一個鄰居是終點
經(jīng)節(jié)點B前往終點需要7分鐘,
原始終點節(jié)點開銷為無窮大,現(xiàn)在7分鐘,更新終點節(jié)點的開銷和父節(jié)點
現(xiàn)在將節(jié)點B設(shè)為被處理過的
接下來要處理的點
獲取節(jié)點A的開銷和鄰居節(jié)點A只有一個鄰居,終點。當前前往終點需要7分鐘,經(jīng)過節(jié)點A需要多久,見圖
更短,更新終點的開銷和父節(jié)點
節(jié)點A被添加進處理過的。
接下來要處理的點為終點fin。
終點fin沒有鄰居,不更新,然后顯示終點也被添加進處理過的。
node變?yōu)榭?#xff0c;退出while循環(huán)。結(jié)束。
如果要改變起點,如起點改到A,再來探討該問題。只需修改costs和parents散列表即可。
算例1借鑒:《算法圖解》
算例2實現(xiàn)
算例1用到的數(shù)據(jù)結(jié)構(gòu)是散列表,本例子用矩陣來做。
第一步:定義圖和拓撲結(jié)構(gòu)
""" 一.定義圖 """ ### ====================給一個所有節(jié)點的列表 list_nodes_id = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]; ### ==================== 給一個拓撲的常數(shù)矩陣. fin= float('inf') # 無窮大。這意味著沒有聯(lián)系。 ### fin_topo是表示拓撲的二維鄰接矩陣 fin_topo = [[fin, 2.9, 2.5, fin, 2, fin, 1.8, 3, 1.8, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin], # 0[2.9, fin, 1.3, fin, fin, 1.7, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin], # 1[1, 1, fin, 1, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin], # 2[fin, fin, 1, fin, 1, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin], # 3[1, fin, fin, 1, fin, fin, fin, fin, fin, 1, 1, 1, fin, fin, fin, fin, fin, fin, fin, fin, fin], # 4[fin, 1, fin, fin, fin, fin, 1, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin], # 5[1, fin, fin, fin, fin, 1, fin, 1, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin], # 6[1, fin, fin, fin, fin, fin, 1, fin, 1, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin], # 7[1, fin, fin, fin, fin, fin, fin, 1, fin, 1, fin, fin, 1, fin, fin, fin, fin, fin, fin, fin, fin], # 8[fin, fin, fin, fin, 1, fin, fin, fin, 1, fin, fin, 1, fin, fin, fin, fin, fin, fin, fin, fin, fin], # 9[fin, fin, fin, fin, 1, fin, fin, fin, fin, fin, fin, 1, fin, 1, fin, fin, fin, fin, fin, fin, fin], # 10[fin, fin, fin, fin, 1, fin, fin, fin, fin, 1, 1, fin, fin, 1, 1, fin, fin, fin, fin, fin, fin], # 11[fin, fin, fin, fin, fin, fin, fin, fin, 1, fin, fin, fin, fin, fin, 1, fin, fin, fin, fin, fin, fin], # 12[fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, 1, 1, fin, fin, 1, fin, fin, 1, 1, fin, fin], # 13[fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, 1, 1, 1, fin, 1, 1, fin, fin, fin, fin], # 14[fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, 1, fin, 1, fin, 1, 1, fin], # 15[fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, 1, 1, fin, fin, fin, fin, 1], # 16[fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, 1, fin, fin, fin, fin, 1, fin, fin], # 17[fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, 1, fin, 1, fin, 1, fin, 1, fin], # 18[fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, 1, fin, fin, 1, fin, 1], # 19[fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, 1, fin, fin, 1, fin], # 20 ]### --- 讀取拓撲,并生成給定拓撲中的所有邊。 edges = [] for i in range(len(fin_topo)):for j in range(len(fin_topo[0])):if i != j and fin_topo[i][j] != fin:edges.append((i, j, fin_topo[i][j])) ### (i,j) 是一個鏈接;fin_topo[i][j]這里是1,鏈接的長度(i,j). #print(edges) #[(0, 1, 2.9), (0, 2, 2.5), (0, 4, 2), (0, 6, 1.8), (edges
第二步 算法
"""算法實現(xiàn)""" from collections import defaultdict from heapq import *#heapq 堆,一般指最小堆 """ heap = [1,3,4,2,6,8,9] heapq.heapify(heap) # heap = [1,2,4,3,6,8,9] """ """ g {0: [(2.9, 1), (2.5, 2), (2, 4), (1.8, 6), (3, 7), (1.8, 8)], 1: [(2.9, 0), (1.3, 2), (1.7, 5)], 2: [(1, 0), (1, 1), (1, 3)], 3: [(1, 2), (1, 4)], 4: [(1, 0), (1, 3), (1, 9), (1, 10), (1, 11)], 5: [(1, 1), (1, 6)], 6: [(1, 0), (1, 5), (1, 7)], 7: [(1, 0), (1, 6), (1, 8)], 8: [(1, 0), (1, 7), (1, 9), (1, 12)], 9: [(1, 4), (1, 8), (1, 11)], 10: [(1, 4), (1, 11), (1, 13)], 11: [(1, 4), (1, 9), (1, 10), (1, 13), (1, 14)], 12: [(1, 8), (1, 14)], 13: [(1, 10), (1, 11), (1, 14), (1, 17), (1, 18)], 14: [(1, 11), (1, 12), (1, 13), (1, 15), (1, 16)], 15: [(1, 14), (1, 16), (1, 18), (1, 19)], 16: [(1, 14), (1, 15), (1, 20)], 17: [(1, 13), (1, 18)], 18: [(1, 13), (1, 15), (1, 17), (1, 19)], 19: [(1, 15), (1, 18), (1, 20)], 20: [(1, 16), (1, 19)]})"""def dijkstra_raw(edges, from_node, to_node):g = defaultdict(list)#使用list作第一個參數(shù),可以很容易將鍵-值對序列轉(zhuǎn)換為列表字典。for l, r, c in edges:g[l].append((c, r))q, seen = [(0, from_node, ())], set()#q=[(0, from_node, ())],q為當前節(jié)點創(chuàng)建一個堆while q:(cost, v1, path) = heappop(q)#刪除q中的頂if v1 not in seen:#如果V1沒有被查看,V1為出發(fā)點seen.add(v1)#將V1添加進已查看path = (v1, path)#(點,路徑)if v1 == to_node:#如果當前點是目標點return cost, path#返回距離和路徑for c, v2 in g.get(v1, ()):#遍歷V1的所有鄰居 c為到鄰居的距離,V2為鄰居if v2 not in seen:heappush(q, (cost + c, v2, path))#修改起點到V2的距離return float("inf"), []全部代碼
""" 一.定義圖 """ # ====================給一個所有節(jié)點的列表 list_nodes_id = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]; ### ==================== 給一個拓撲的常數(shù)矩陣. fin= float('inf') # 無窮大。這意味著沒有聯(lián)系。 #fin_topo是表示拓撲的二維鄰接矩陣 fin_topo = [[fin, 2.9, 2.5, fin, 2, fin, 1.8, 3, 1.8, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin], # 0[2.9, fin, 1.3, fin, fin, 1.7, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin], # 1[1, 1, fin, 1, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin], # 2[fin, fin, 1, fin, 1, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin], # 3[1, fin, fin, 1, fin, fin, fin, fin, fin, 1, 1, 1, fin, fin, fin, fin, fin, fin, fin, fin, fin], # 4[fin, 1, fin, fin, fin, fin, 1, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin], # 5[1, fin, fin, fin, fin, 1, fin, 1, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin], # 6[1, fin, fin, fin, fin, fin, 1, fin, 1, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin], # 7[1, fin, fin, fin, fin, fin, fin, 1, fin, 1, fin, fin, 1, fin, fin, fin, fin, fin, fin, fin, fin], # 8[fin, fin, fin, fin, 1, fin, fin, fin, 1, fin, fin, 1, fin, fin, fin, fin, fin, fin, fin, fin, fin], # 9[fin, fin, fin, fin, 1, fin, fin, fin, fin, fin, fin, 1, fin, 1, fin, fin, fin, fin, fin, fin, fin], # 10[fin, fin, fin, fin, 1, fin, fin, fin, fin, 1, 1, fin, fin, 1, 1, fin, fin, fin, fin, fin, fin], # 11[fin, fin, fin, fin, fin, fin, fin, fin, 1, fin, fin, fin, fin, fin, 1, fin, fin, fin, fin, fin, fin], # 12[fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, 1, 1, fin, fin, 1, fin, fin, 1, 1, fin, fin], # 13[fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, 1, 1, 1, fin, 1, 1, fin, fin, fin, fin], # 14[fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, 1, fin, 1, fin, 1, 1, fin], # 15[fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, 1, 1, fin, fin, fin, fin, 1], # 16[fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, 1, fin, fin, fin, fin, 1, fin, fin], # 17[fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, 1, fin, 1, fin, 1, fin, 1, fin], # 18[fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, 1, fin, fin, 1, fin, 1], # 19[fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, 1, fin, fin, 1, fin], # 20 ]#--- 讀取拓撲,并生成給定拓撲中的所有邊。 edges = [] for i in range(len(fin_topo)):for j in range(len(fin_topo[0])):if i != j and fin_topo[i][j] != fin:edges.append((i, j, fin_topo[i][j])) ### (i,j) 是一個鏈接;fin_topo[i][j]這里是1,鏈接的長度(i,j). #print(edges) #[(0, 1, 2.9), (0, 2, 2.5), (0, 4, 2), (0, 6, 1.8), (#算法實現(xiàn) from collections import defaultdict from heapq import *def dijkstra_raw(edges, from_node, to_node):g = defaultdict(list)#使用list作第一個參數(shù),可以很容易將鍵-值對序列轉(zhuǎn)換為列表字典。for l, r, c in edges:g[l].append((c, r))q, seen = [(0, from_node, ())], set()#q=[(0, from_node, ())],q為當前節(jié)點創(chuàng)建一個堆while q:(cost, v1, path) = heappop(q)#刪除q中的頂if v1 not in seen:#如果V1沒有被查看,V1為出發(fā)點seen.add(v1)#將V1添加進已查看path = (v1, path)#(點,路徑)if v1 == to_node:#如果當前點是目標點return cost, path#返回距離和路徑for c, v2 in g.get(v1, ()):#遍歷V1的所有鄰居 c為到鄰居的距離,V2為鄰居if v2 not in seen:heappush(q, (cost + c, v2, path))return float("inf"), []start = int(input("請輸入起始點:")) end = int(input("請輸入終點:")) print("=== Dijkstra算法 ===") print("找從 %s 到 %s的最短路徑:" % (start, end)) length, path_queue = dijkstra_raw(edges,start, end)#path_queue路徑 print('距離',length) print('路徑',path_queue)0到15的距離為4.8,路徑0-8-12-14-15
工具包講解
參考我以前的博文
最短路徑Dijkstra講解,工具包使用 python
作者:電氣-余登武
總結(jié)
以上是生活随笔為你收集整理的用图讲解狄克斯特拉(DiskStra)算法,python实现 。的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 基金定投有什么优势和劣势 注意这几点就
- 下一篇: python 多线程讲解(如何实现多线程