在一个赋权图中,从一个顶点出发到达另外一个顶点的路的权和最小的路径,称为最短路径。Dijkstra算法是解决这一问题的经典算法,接下来我们介绍如何用python实现这一算法。
Dijkstra算法由Dijkstra在1959年发现,这个算法不仅找到了最短的 ( u 0 , v 0 ) (u_0,v_0) (u0,v0) 路,而且给出了从 u 0 u_0 u0 到其他所有顶点的最短路。
假设 S S S 是 V V V 的真子集且有 u 0 ∈ S u_0\in S u0∈S,记 S ‾ \overline{S} S 为 V − S V-S V−S。若 p a t h = u 0 ⋯ u v ‾ path=u_0\cdots \overline{uv} path=u0⋯uv,这里 u ‾ ∈ S \overline{u} \in S u∈S 则 ( u 0 , u ‾ ) (u_0,\overline{u}) (u0,u) 一定是最短路。所以
故 u 0 u_0 u0 到 S ‾ \overline{S} S 距离为:
这里 u ∈ S , v ∈ S ‾ u\in S,v\in \overline{S} u∈S,v∈S。那么我们可以思考,我们可以从集合 { u 0 } \{u_0\} {u0} 开始构造一个递增序列 S 1 , S 2 , ⋯ , S v − 1 S_1,S_2,\cdots ,S_{v-1} S1,S2,⋯,Sv−1,这样我们便可以得到由 u 0 u_0 u0 到 S i S_i Si 的所有顶点的最短路。
优美的代码来了
import heapq
class graph:
def __init__(self,num,ma):
self.edge={}
self.dic=[ma]*(num+1)
def add(self,u,v,w):
if u in self.edge:
self.edge[u].append((v,w))
else:
self.edge[u]=[(v,w)]
def dijkstra(self,start):
dis=self.dic
dis[start]=0
heap=[]
visit=[0 for i in range(len(dis))]
for i in self.edge[start]:
dis[i[0]]=i[1]
heapq.heappush(heap,(i[1],i[0]))# i[1]为该边权值,i[0]为该点,从start点到其余点的边入堆
while heap!=[]:
vic=heapq.heappop(heap)# 弹出最小边
if visit[vic[1]]==1:# 如果该点已经弹出过,则不再松弛
continue
visit[vic[1]] = 1# 记录弹出点
if vic[1] not in self.edge:# 防止有向图中出度为0的点
continue
for i in self.edge[vic[1]]:
if dis[i[0]]>dis[vic[1]]+i[1]:# 判断是否松弛
dis[i[0]]=dis[vic[1]]+i[1]# 松弛边
heapq.heappush(heap,(i[1],i[0]))# 松弛过边则把新边权值入堆
self.dic=dis
def printf(self):
print(self.dic[1:])
if __name__ == "__main__":
n,m=6,8
nums = [[1,3,10],[1,5,30],[1,6,100],[2,3,5],[3,4,50],[4,6,10],[5,6,60],[5,4,20]]
g=graph(n,1000000000000)
for i in nums:
g.add(i[0],i[1],i[2])
g.dijkstra(1)
g.printf()
我们在算法第二步求最小的点 u i + 1 u_{i+1} ui+1时可用堆做优化。在python中有模块heapq,这里我们直接使用该模块的最小堆即可。
注意:dijkstra算法不能求解有负权边地图(思考一下为什么?)。