學習材料
《算法圖解》第7章
適用情景
找出從一個節點到另一個節點的最短(快)路徑
準備工作
對一個帶權圖進行描述,可使用兩個散列表(字典),其中一個散列表用來描述每個結點的指向及權值,對於一個節點指向兩個及以上的節點的情況,可使字典嵌套;另一個散列表用來描述從“起點”開始到其他節點的距離,若起點未與某個節點直接相連,在初始時把這段距離置爲無窮大。
舉例:圖及其對應散列表如下
graph={}
graph["start"]={}
graph["start"]["A"]=5
graph["start"]["B"]=2
graph["A"]={}
graph["A"]["C"]=4
graph["A"]["D"]=2
graph["B"]={}
graph["B"]["A"]=8
graph["B"]["D"]=7
graph["C"]={}
graph["C"]["fin"]=3
graph["C"]["D"]=6
graph["D"]={}
graph["D"]["fin"]=1
graph["fin"]={}
costs={}
costs["A"]=5
costs["B"]=2
costs["C"]=float('inf')
costs["D"]=float('inf')
costs["fin"]=float('inf')
爲了記錄最短路徑,還需要一個散列表描述最短路徑中的父子關係。
parents={}
parents["A"]="start"
parents["B"]="start"
parents["C"]=""
parents["D"]=""
parents["fin"]=""
執行算法時,將不斷更新costs和parents。
算法流程
(1)找出costs中對應值最小的結點,且未被處理過(未在processed中)
(2)檢查該節點的鄰居,尋找是否有從起點開始經過該節點到其鄰居的更短路徑,若有,則更新其鄰居在costs的對應值
(3)重複(1)(2),直到對每個節點都做過這件事
(4)計算最短路徑
代碼如下:
processed=[]
def find_lowest_cost_node(costs):
lowest_cost=float('inf')
lowest_cost_node=None
for node in costs: #遍歷所有節點
cost=costs[node]
if cost<lowest_cost and node not in processed:
#如果當前節點與起點距離的更小,且未被處理過
#則將其視爲最小開銷節點
lowest_cost=cost
lowest_cost_node=node
return lowest_cost_node
node=find_lowest_cost_node(costs) #在未被處理過的節點中找出最小開銷節點
while node is not None: #循環將在所有節點都被處理過後結束
cost=costs[node]
neighbors=graph[node]
for n in neighbors.keys(): #遍歷當前節點所有鄰居
newcost=cost+neighbors[n]
if costs[n]>newcost: #如果經過當前節點前往該鄰居更近
costs[n]=newcost #則更新起點到該鄰居的距離(所謂的該鄰居的開銷)
parents[n]=node #同時將該鄰居的父節點設置爲當前節點
processed.append(node) #當前節點標記爲已處理
node=find_lowest_cost_node(costs) #找出下一個在costs中的最小開銷節點,進入下一輪循環
輸出最短路徑及最短距離(測試):
def lowest_path(parents):
record=["fin",]
child=parents["fin"]
while child != "start":
record.append(child)
child=parents[child]
record.append("start")
record.reverse()
return record
def tatal_weight(record):
record1=record[1::]
sum=0
for i in range(len(record1)):
sum=sum+graph[record[i]][record1[i]]
return sum
print(lowest_path(parents))
print(tatal_weight(lowest_path(parents)))
輸出結果
['start', 'A', 'D', 'fin']
8
算法弊端
(1)不能用於有負權值的圖,因爲在該算法中,已處理過的點意味着從起點到該點的開銷已是最小,且不會再對該點進行處理,但負權值可能會拉低這個最小開銷。
(2)不能使用於有環圖。只適用於有向無環圖。
(3)有負權值的圖可使用貝爾曼—福德算法,非加權圖可使用廣度優先搜索算法。