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