从分支限界的角度来看Dijkstra算法:
Dijkstra算法是基于贪心的广度优先搜索,也可以看成分支限界法,从分支限界的角度来看,Dijkstra算法看起来就更加清晰明了
代码实现:
# =============================================================================
# 分支限界法,dijkstra算法
# 首先解空间[起点0,顶点1,当点2,...,顶点n-1]看成[起点,剩余n-1个顶点的排列树]
# 下界函数bound()是什么了?
# 可以参考货郎问题:下届为 已走过的路+未走过的路(未走过的结点的最小出边和)
# 同时我们可以优化下界函数,我们知道起点到最后一个顶点的最短距离,那么他途径得的顶点
# 都是最短距离,那么我们可以把顶点到起点的最短距离当作下界
# =============================================================================
import heapq
def dijkstra(graph,start):
# 解空间的长度
vnum = len(graph)
# 优先级队列
pqueue = []
# 把起点放入队列,[顶点到起点的距离,前驱结点,当前结点]
heapq.heappush(pqueue,(0.0,None,start))
# 有两个用途:一是用来标记那些顶点已经访问了,二是用于存储前驱结点,用于解的追踪
# 在这里为什么可以用它标记已访问的结点和追踪解,实际上这种贪心策略,已经保证到底时,就
# 可以得到最优解了,一般的限界分支,可能还需要跑到离root很近的结点再往下走,每个结点
# 需要记录前驱结点和已访问的结点。
paths = {vertex : None for vertex in graph}
# depth可以放在活结点里
depth = 0
# 循环体出口时是解满了,之前看起来很神奇的出口,现在就看起来很平常了
while depth < vnum :
# 弹出结点
pair = heapq.heappop(pqueue)
distance = pair[0]
parent = pair[1]
vertex = pair[2]
# 加入结点已经访问过了,就直接弹出下一个结点
if paths[vertex]:
continue
# 把该节点到起点的最短距离和前驱结点保存起来,并记录该节点已经访问过了
paths[vertex] = (parent,distance)
# 把该节点的子节点放入优先级队列,这里进行了剪枝,只放入和当前结点相连的结点
# 不相连的结点,认为距离为无穷,没有必要放入到优先级队列
# 以下是相连的边的终点顶点,把他们放入优先级队列
edges = graph[vertex]
for v in edges:
# 加入终结点已经访问过,就没有必要加入,这是一个排列树
if paths[v] is None:
# 把这个顶点到起点的距离,这个结点,前驱结点加入队列,
# 这个顶点到起点的距离:源节点的最短距离+他们的边的长度
heapq.heappush(pqueue,(distance + graph[vertex][v],vertex,v))
# 进入下一层,这个策略由于上面的continue保证了,每次到这儿可以进入下一层了,
# 也可以把depth放到活结点里,放到活结点,就必须要注意循环体的退出
depth += 1
return paths
g = {'A':{'B':1,'C':2},
'B':{'A':1,'C':3,'D':4},
'C':{'A':2,'B':3,'D':5,'E':6},
'D':{'B':4,'C':5,'E':7,'F':8},
'E':{'C':6,'D':7,'G':9},
'F':{'D':8},
'G':{'E':9}
}
t=dijkstra(g,'A')
print(t)
runfile('D:/share/test/dijkstra_prune.py', wdir='D:/share/test')
{'A': (None, 0.0), 'B': ('A', 1.0), 'C': ('A', 2.0), 'D': ('B', 5.0), 'E': ('C', 8.0), 'F': ('D', 13.0), 'G': ('E', 17.0)}