图搜索算法-最短路径算法-戴克斯特拉算法

相关文章:
数据结构–图的概念
图搜索算法 - 深度优先搜索法(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语言实现》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值