相关文章:
数据结构–图的概念
图搜索算法 - 深度优先搜索法(DFS)
图搜索算法 - 广度优先搜索法(BFS)
图搜索算法 - 拓扑排序
最短路径算法
自从有了导航,人们再也不怕去陌生地方,说走就走的旅行一点都不困难。打开导航,它总能告诉你怎样去到想要去的地方,并且往往已经是优化了的路线,路程最短或用时最少。社交应用已经占据手机使用的大部分时间了,在微信,英领,QQ等平台上查看朋友简介,平台也会告诉我们谁是大家共同朋友,或者告诉你,某个人和你有共同朋友,要不你们也认识一下。这些都是应用了最短路径算法,通过算法可以快速给出两点之间的最短距离,可以计算两点间成本最低的路线。解决最短路的问题有好几种算法,戴克斯特拉(Dijkstra)算法,贝尔曼-福特(Bellman-Ford)算法,插点算法(Floyd)等。
戴克斯特拉算法(Dijkstra)
虽然导航好像在最近10几年才普及使用,但早在1960年美国已经开始利用军事卫星来做导航,而戴克斯特拉(Dijkstra)算法早在1956年被荷兰计算机科学家艾兹赫尔·戴克斯特拉提出。它用于求解从单源结点到图中所有其他结点的最短路径,下面来分析这个算法是怎样运行的,首先来构建一个有权无向图,如图所示。
(1)设定结点【A】为源结点。
(2)创建一个集合【short_path_set】记录已计算的最小距离结点,也称为最短路径树集合。刚开始的时候,集合为空。
(3)创建一个字典【distance】记录源结点与每个结点的最小距离值,初始化为源结点的距离0,其余结点为无穷大(Max),【“A”:0, Max, Max, Max, Max, Max, Max, Max, Max】。现在把源结点放到【short_path_set】集合中,然后它相邻的结点是【D】和【E】,这时候可以更新【distance】为【“A”:0, “D”:4, “E”:6, Max, Max, Max, Max, Max, Max】,如图所示。
注意:圆圈内的代表是结点值,上面的数值代表结点到源结点的距离,灰色圆圈代表已经在【short_path_set】集合中。
(4)选取【distance】里面最小距离值,并且不在【short_path_set】集合中的结点,因此选择结点【D】,并放进集合中。然后更新结点【D】相邻结点的距离,如图所示。
(5)按同样的方式寻找下一个结点,此时为结点【E】,然后更新该结点邻近结点的距离,如图所示。
(6)这时候【distance】的值为【“A”:0, “D”:4, “E”:6, “B”:12, “H”:17, “F”:10, Max, Max, Max】,【short_path_set】的值为(“A”,“D”,“E”),这时候按同样方式挑选结点【F】,然后更新邻接节点,便发现结点【H】的距离发生改变,如图所示。
(7)然后挑选结点【B】,然后更新邻接节点,这个时候结点【H】的距离又缩小为13,如图所示。
(8)同理挑选结点【C】,然后更新邻接节点,如图所示。
(9)后面按同样方式挑选结点【H】,再到结点【M】,最后是结点【J】,直到所有结点都包含到【short_path_set】集合中,最后结果如图所示。
最终【distance】的值为【“A”:0, “D”:4, “E”:6, “B”:12, “H”:13, “F”:10, “C”:12, “M”:19, “J”:22】。此算法需要遍历所有结点,而且要对每个结点遍历所有邻接结点,因此极端情况下,所有结点都互相邻接,那么它的时间复杂度为O(N^2),N为结点数量。空间上主要用到一个大小为N的【distance】列表来保存每个结点到源结点的最小距离,因此空间复杂度为O(N)。
注意:这个算法和前面最小生成树里面的普里姆算法非常相似,普里姆算法中的结点键值不是累加起来,而是一条边上的权重和相邻结点的键值和。
现在用代码把上面的运算过程表现出来,首先创建【GraphDijkstra】类继承【GraphArray】类带有邻接矩阵录入功能,dijkstra()函数是算法主程序,最后结果通过print_result()函数输出每个结点到源结点的最短距离。
import sys # 引入系统参数,获取系统支撑的最大值
class GraphDijkstra(GraphArray):
"""这是寻找单源结点到其余结点的最短路径"""
def print_result(self, dist):
print("结点 \t距离源结点的距离")
for v in range(self.amount):
print(self.points[v], "\t", dist[v])
def min_distance(self, dist, short_path_set):
# 寻找结点到源结点最短距离
min_dist = sys.maxsize #初始化最小距离为系统最大值
for v in range(self.amount):
# 寻找最小距离的结点,并且不在最短路径树集合中
if dist[v] <= min_dist and v not in short_path_set:
min_dist = dist[v]
min_index = v
return min_index
def dijkstra(self, source):
# dijkstra算法主程序,遍历所有结点,放进short_path_set集合中,求得所有结点到源结点的最小距离
distance = [sys.maxsize] * self.amount
distance[source] = 0
short_path_set = set() # 初始化集合为空
for _ in range(self.amount):
# 寻找最小距离的结点,并且不包含在最短路径树集合中
# 而且源结点总是作为第一个结点进入集合
u = self.min_distance(distance, short_path_set)
short_path_set.add(u) # 把结点添加到集合中
# 重新遍历邻接结点,更新结点间最小距离的值
for v in range(self.amount):
if self.graph[u][v] > 0 and v not in short_path_set and distance[v] > distance[u] + self.graph[u][v]:
distance[v] = distance[u] + self.graph[u][v] #符合条件,更新最短路径值
self.print_result(distance) # 输出结果
根据例子,构建一个邻居矩阵作为输入,来验证程序。
g = GraphDijkstra(['A','B','C','D','E','F','H','M','J'])
g.graph = [[0, 0, 0, 4, 6, 0, 0, 0, 0],
[0, 0, 9, 8, 0, 0, 1, 7, 0],
[0, 9, 0, 0, 0, 2, 0, 11, 10],
[4, 8, 0, 0, 0, 0, 13, 0, 0],
[6, 0, 0, 0, 0, 4, 0, 0, 0],
[0, 0, 2, 0, 4, 0, 5, 0, 0],
[0, 1, 0, 13, 0, 5, 0, 0, 0],
[0, 7, 11, 0, 0, 0, 0, 0, 3],
[0, 0, 10, 0, 0, 0, 0, 3, 0]
];
g.dijkstra(0) # A作为源结点
# -------------结果-------------------
结点 距离源结点的距离
A 0
B 12
C 12
D 4
E 6
F 10
H 13
M 19
J 22
对照例子最后的结果,两者是一致的。大家可以尝试换一个源结点,如把结点【B】换作为源结点,再重新计算结果,如下所示。
g.dijkstra(1) # B作为源结点
这样将会得到完全不一样的结果,大家可以多运行几次,以此熟悉算法运算过程。
更多内容
想获取完整代码或更多相关图的算法内容,请查看我的书籍:《数据结构和算法基础Python语言实现》