之前在做SDN网络控制器时,写过一个dijkstra的最短路径算法,现在回顾重温一下。
以下面的图为测试样本:
dijkstar算法的大致思想是(假设起点为A):每新到达一个点V,探测所有与该点直接相邻的点(V’)及之间的距离d(V,V’),如果V`是第一次探测到,那么记录其距离d(A,V’) = d(A,V) + d(V,V’),如果已有记录但d(A,V) + d(V,V’)小于之前的记录,则更新记录;直到所有点都被遍历。
依旧,为了方便,使用python语言,图用边的集合来表示:
1 2
| g = [("A", "B", 6), ("A", "C", 3), ("B", "C", 2), ("B", "D", 5), ("C", "D", 3), ("C", "E", 4), ("D", "F", 3), ("D", "E", 2), ("E", "F", 6)]
|
初始状态
从A开始探测,则已知距离d(A,A) = 0 。
对单个点的处理
1 2 3 4 5 6 7 8 9 10 11
| map={"A":0} #初始记录 #对点"A"进行处理 for e in g: #遍历所有边 (e = edge) if "A" in e[0:2]: #与A相连的边 d = map["A"]+e[2] #计算距离 #添加记录(初始map为空,暂不考虑记录已存在的情况) if "A" == e[0]: #无向边,需要确定另一顶点名称 map[e[1]] = d else: map[e[0]] = d print map
|
输出
1
| {'A': 0, 'C': 3, 'B': 6}
|
一轮探测完以后,记录中新增了B C两点及它们与起点A的距离 ,那么接下来就要对B,C依次进行同样的处理了,随后对与B,C相连的点的处理……直到所有点处理完。
完成算法(使用队列)
对旧有代码进行改造:
- 将新探测到的点加入队列(待处理对象)
- 更新记录时与旧有记录作比较
增加以上功能后的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| map={"A":0} vp = Queue.Queue(maxsize=0) vp.put("A") while(not vp.empty()): v=vp.get() for e in g: if v in e[0:2]: d = map[v]+e[2] if v == e[0]: v1 = e[1] else: v1 = e[0] if v1 not in map: vp.put(v1) map[v1] = d elif d < map[v1]: map[v1] = d print map
|
输出为:
1
| {'A': 0, 'C': 3, 'B': 5, 'E': 7, 'D': 6, 'F': 9}
|
将图拿来确认一下正确性。(算法到此已可算完成了。)
记录路径
以上只是输出了最短距离,但并没有记录路径,再稍微改造一下,并改以B为起点,并增加一些打印信息,方便了解算法运行过程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| map={"B":["B",0]} vp = Queue.Queue(maxsize=0) vp.put("B") #first step while(not vp.empty()): v=vp.get() #对待处理点v,执行 print "dealing with: "+v for e in g: #遍历边集 if v in e[0:2]: #与v相连的边 d = map[v][1]+e[2] #计算距离 if v == e[0]: #无向边,需要确定另一顶点v1 v1 = e[1] else: v1 = e[0] if v1 not in map: #v1不在记录中 print "adding :"+v1 map[v1] = [map[v][0]+v1,d] #加入记录 print map vp.put(v1) #将v1加入待处理点 elif d < map[v1][1]: #已有记录,但新路径更短 print "update :"+v1 map[v1] = [map[v][0]+v1,d] #更新记录 print map
|
输出为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| dealing with: B adding :A {'A': ['BA', 6], 'B': ['B', 0]} adding :C {'A': ['BA', 6], 'C': ['BC', 2], 'B': ['B', 0]} adding :D {'A': ['BA', 6], 'C': ['BC', 2], 'B': ['B', 0], 'D': ['BD', 5]} dealing with: A dealing with: C update :A {'A': ['BCA', 5], 'C': ['BC', 2], 'B': ['B', 0], 'D': ['BD', 5]} adding :E {'A': ['BCA', 5], 'C': ['BC', 2], 'B': ['B', 0], 'E': ['BCE', 6], 'D': ['BD', 5]} dealing with: D adding :F {'A': ['BCA', 5], 'C': ['BC', 2], 'B': ['B', 0], 'E': ['BCE', 6], 'D': ['BD', 5], 'F': ['BDF', 8]} dealing with: E dealing with: F
|
再次将图拿来确认一下正确性。
算法至此完成。
总结
dijkstar算法的正确性是如何保证的呢?因为算法在任一时刻,都保证了:
- 记录中的项是已探明路径中的最短路径。起始状态,结论显然成立:到自身距离为0。
- 探测了与所有当前点相邻的点(发现所有新路径)。对于新探测到的点,第一条路径也是唯一路径,自然是已知最短的;对于已有记录的点,则用更短路径(如果有)来更新;从而再次保证了1的成立。
当所有点都处理完,所有路径探明,记录自然就成为了到达所有点的最短路径。所以dijkstra算法其实是一个遍历所有路径,保留最小值的算法,只是选用了一个比较巧妙的遍历方式。