算法原理
1. 简介
Dijkstra算法是从一个顶点到其余各顶点的最短路径(单源最短路径)算法,解决的是有向图中最短路径问题。算法的主要特点是使用了广度优先搜索策略,以起始点为中心向外层层扩展,直到扩展到终点为止。Dijkstra算法能得出最短路径的最优解,但由于它遍历计算的节点很多,所以效率低。
2. 原理
- 首先,引入一个辅助向量DD,它的每个分量 保存当前所找到的从起始点vv到其它每个顶点的长度;一个保存已经找到了最短路径的顶点的集合:TT
- 的初始状态为: 若从vv到 有弧,则D[i]D[i]为弧上的权值否则置D[i]D[i]为∞;
TT的初始状态为 - 从DD数组选择最小值,则该值就是源点到该值对应的顶点的最短路径,并且把该点加入到TT中,OK,此时完成一个顶点
- 然后,我们需要看看新加入的顶点是否可以到达其他顶点并且看看通过该顶点到达其他点的路径长度是否比源点直接到达短,如果是,那么就替换这些顶点中的值
- 从DD中找出最小值,重复上述动作,直到T中包含了图的所有顶点
算法演示
对于如下图,求顶点到其他各点的最短路径
初始化数D=[0,inf,10,inf,30,100]D=[0,inf,10,inf,30,100], T={v1}T={v1}
当前离起始点v1v1最近的点是v3v3, 那么我们可以确定v1v1和v3v3的最近距离就是当前D[2]D[2]的值, 并将v3v3加入TT中 (为什么呢?因为目前离 v1顶点最近的是 v3顶点,并且这个图所有的边都是正数,那么肯定不可能通过第三个顶点中转,使得 v1顶点到 v3顶点的路程进一步缩短了。因为 v1顶点到其它顶点的路程肯定没有 v1到 v3顶点短.)
OK,既然确定了一个顶点的最短路径,下面我们就要根据这个新入的顶点的出度,发现以v3v3的出度的有:{v4}{v4},那么我们看看路径:v1–v3–v4v1–v3–v4的长度是否比v1–v4v1–v4短,其实这个已经是很明显的了,因为D[3]D[3]代表的就是v1–v4v1–v4的长度为无穷大,而v1–v3–v4v1–v3–v4的长度为:10+50=60,所以更新D[3]D[3]的值,得到如下结果:
D=[0,inf,10,60,30,100]D=[0,inf,10,60,30,100], T={v1,v3}T={v1,v3}
(因此 D[3]D[3]要更新为 60。这个过程有个专业术语叫做“松弛”。即 v1v1顶点到 v4v4顶点的路程即 D[3]D[3],通过 v3−v4v3−v4 这条边松弛成功。这便是 Dijkstra 算法的主要思想:通过“边”来松弛v1v1顶点到其余各个顶点的路程)
然后,我们又从除v1v1和v3v3外的其他值中寻找最小值,发现D[4]D[4]的值最小,通过之前是解释的原理,可以知道v1到v5v1到v5的最短距离就是D[4]D[4]的值,然后,我们把v5v5加入到集合T中,然后,考虑v5v5的出度是否会影响我们的数组DD的值,有两条出度:{v4,v6}{v4,v6},然后我们发现:v1–v5–v4v1–v5–v4的长度为:50,而v1−v4v1−v4的值为60,所以我们要更新D[3]D[3]的值.另外,v1−v5−v6v1−v5−v6的长度为:90,而v1−v6v1−v6为100,所以我们需要更新D[5]D[5]的值。更新结果如下:
D=[0,inf,10,50,30,90]D=[0,inf,10,50,30,90], T={v1,v3,v5}T={v1,v3,v5}
继续从DD中选择未确定的顶点的值中选择一个最小的值,发现的值是最小的,所以把v4v4加入到集合TT中,考虑的出度,v4有一条出度:{v6}{v6},然后我们发现:v1–v5–v4–v6v1–v5–v4–v6的长度为:60,而v1−v6v1−v6的值为90,所以我们要更新D[5]D[5]的值, 结果如下
D=[0,inf,10,50,30,60]D=[0,inf,10,50,30,60], T={v1,v3,v5,v4}T={v1,v3,v5,v4}
用同样的方法确定了v6v6和v2v2的最短路径,得到的最终结果如下:
D=[0,inf,10,50,30,60]D=[0,inf,10,50,30,60], T={v1,v3,v5,v4,v6,v2}T={v1,v3,v5,v4,v6,v2}
算法实现
# coding:utf-8
def dijkstra(G, v):
"""
:param G: graph, type of dict
:param v: original node, type of str
:return: signal source shortest path,
type of list
"""
# initialize
n = len(G)
D = [float('inf')] * n
D[node2ind(v)] = 0
S = set(G.iterkeys())
# iteration
while S:
v = None
for node in S:
if v == None or D[node2ind(node)] < D[node2ind(v)]:
v = node
S.remove(v)
for node in G[v]:
ind = node2ind(node)
# 如果集合为空或者满足三角不等式,则更新D
if D[node2ind(v)] + G[v][node] < D[ind]:
D[ind] = D[node2ind(v)] + G[v][node]
return D
def node2ind(node):
"""get index of node"""
ind = int(node[-1]) - 1
return ind
def ind2node(ind):
"""get node given index"""
node = 'v' + str(ind + 1)
return node
if __name__ == '__main__':
G = {
'v1':{'v3':10, 'v5':30, 'v6':100},
'v2':{'v3':5},
'v3':{'v4':50},
'v4':{'v6':10},
'v5':{'v4':20, 'v6':60},
'v6':{}
}
v = 'v1'
res = dijkstra(G, v)
print(res)
"""打印的结果"""
[0, inf, 10, 50, 30, 60]
复杂度分析
该算法时间复杂度为O(n2)O(n2), 但是,如果边数远小于n2n2,对此可以考虑用堆这种数据结构进行优化,取出最短路径的复杂度降为O(1)O(1);每次调整的复杂度降为O(elogn)O(elogn);ee为该点的边数,所以复杂度降为, mm为总的边数
堆优化算法步骤
- 将与源点相连的点加入堆,并调整堆。
- 选出堆顶元素(即代价最小的元素),从堆中删除,并对堆进行调整。
- 处理与uu相邻的,未被访问过的,满足三角不等式的顶点
1):若该点在堆里,更新距离,并调整该元素在堆中的位置。
2):若该点不在堆里,加入堆,更新堆。 - 若取到的为终点,结束算法;否则重复步骤2、3。
堆优化算法实现
def dijkstra_heap(G, v):
# initialize
n = len(G)
D = [float('inf')] * n
D[node2ind(v)] = 0
T = set()
heap = []
# iteration
while len(T) < n:
for node in G[v]:
ind = node2ind(node)
# 如果集合为空或者满足三角不等式,则更新D
if len(T) == 0 or D[node2ind(v)] + G[v][node] < D[ind]:
D[ind] = D[node2ind(v)] + G[v][node]
heappush(heap, (D[node2ind(v)] + G[v][node], node))
T.add(v)
# find node minimum length to v
if len(T) < n:
node = heappop(heap)[1]
while node in T:
# 如果堆中没有元素了
if not heap:
return D
node = heappop(heap)[1]
v = node
return D
def node2ind(node):
"""get index of node"""
ind = int(node[-1]) - 1
return ind
def ind2node(ind):
"""get node given index"""
node = 'v' + str(ind + 1)
return node
if __name__ == '__main__':
G = {
'v1':{'v3':10, 'v5':30, 'v6':100},
'v2':{'v3':5},
'v3':{'v4':50},
'v4':{'v6':10},
'v5':{'v4':20, 'v6':60},
'v6':{}
}
v = 'v1'
res = dijkstra_heap(G, v)
print(res)
"""打印的结果是"""
[0, inf, 10, 50, 30, 60]