python有向图带权最短路径_有向图的最短路径问题(二)——Dijkstra算法

在有向图的最短路径问题(一)中我们介绍了可以求任意两点间最短距离的Floyd算法,虽然Floyd算法实现起来简单,但是其效率较低(O(n**3))。在本篇我们将介绍求某一点到图中所有点最短距离的算法——Dijkstra算法。

Dijkstra算法

image.png

使用Dijkstra算法求某一点到图中所有点最短距离有两种方法,分别使用的数据结构为邻接矩阵(python中以一个多重列表或矩阵表示)和邻接表(python中使用字典来表示)。但是无论使用那种数据结构,他的算法原理都是相同的,以上图为例,具体可分为下面四步:

(1)找出距离当前节点(即起点S)最近的节点,也即权重最小的边,前往该节点(即B);

(2)对于该节点(B)的邻居,较之已有的路径,检查是否有前往他们更短路径,如果有,就更新其开销(譬如SA=6,但是经过B点再到A的开销为5,小于6,更新);

(3)更新完所有邻居节点的开销后,又回到第(1)步,即选择距离当前节点最近的节点(已前往过的节点跳过),前往该节点并更新邻居的开销;循环这个过程,直到每个节点都走过;

(4)计算最终路径。

1、使用邻接矩阵

image.png

他的邻接矩阵e表示如下:

image.png

此外还需要初始化一个起始点到所有点的距离开销的数组(为方便我们命名为dis),后续我们更新的开销即为此数组中的开销:

初始距离

1)查找距离起点最近的点,即为dis中的最小的点,此处为dis[2]=1,即我们确定离起点最近的点一定为2;

2)接着我们更新点2相邻点<1,3,4>的开销,2_3=9, 2_4=3,然后我们比较经过2到达3,4的开销与1直接到达3,4(即dis[3],dis[4])的开销,我们发现dis[3]=12大于dis[2]+2_3=10,于是更新1到3的开销;同样的dis[4]=∞大于dis[2]+2_4=4,于是也更新dis[4]的开销为4(由于1为起点,所以这里不用考虑dis[1]与1_2+2_1的关系,虽然计算也是小于后者);更新完的dis数组如下所示:

更新点2相邻点

3)继续寻找剩余的点(3,4,5,6)中距离起点最近的点(此处为dis[4]=4,即确定了点4离点1的最近距离为4)来更新相邻点<2,3,5,6>的开销。dis[2]为已经确定了的,不用计算,虽然计算了也是不用更新。dis[3]=10大于dis[4]+4_3=8,于是更新其开销为8,同理更新dis[5]=17,dis[6]=19,结果如下:

更新点4相邻点

4)然后继续寻找剩下点(3,5,6)中的最小值,此处为3,也即确定了起点到点3的最近距离为dis[3]=8,然后更新其相邻点,结果为:

更新点3相邻点

5)继续寻找剩余的点(5,6)中的最小值,此处为5,即确定了起点到点5的最近距离为dis[5],更新相邻点结果为:

更新点5

6)最后确定6号点的最小值,更新其相邻点的开销(其实也没改变)。完成,1号点到所有点的最小值。

下面是上述例子的Dijkstra算法python实现代码:

# edges表示邻接矩阵

edges = [[0, 1, 12, 666, 666, 666],

[666, 0, 9, 3, 666, 666],

[666, 666, 0, 666, 5, 666],

[666, 666, 4, 0, 13, 15],

[666, 666, 666, 666, 0, 4],

[666, 666, 666, 666, 666, 0]]

dis = {2: 1, 3: 12, 4: 666, 5: 666, 6: 666}

visited = []

min_dis = None

min_dis_point = None

for i in range(len(dis)):

sort_dis = sorted(dis.items(), key=lambda item: item[1])

# 找到dis中距离起始点距离最小的点

for p, d in sort_dis:

if p not in visited:

min_dis_point = p

min_dis = d

visited.append(p)

break

for j in range(len(edges)):

# 权重小于666的为相邻点

if edges[min_dis_point-1][j] < 666:

update = min_dis + edges[min_dis_point-1][j]

# 若经过min_dis_point到j的距离比起点直达j的距离大,则更新

if dis[j+1] > update:

dis[j+1] = update

print(dis)

# 由于python中列表的索引是从0开始的,而我们的点是1-6,因此min_dis_point在邻接矩阵中的位置需要减一,

# 这里容易犯错,若是将点改为从0开始无论是写还是可读性都会好很多

2、使用邻接表

无论使用哪种数据结构,算法原理都是一样的,因此这里仅讨论使用邻接表时如何用Python代码实现。

由于python中没有指针,因此这里需要用嵌套字典来表示邻接表,形式为{节点:{相邻节点:连线权重}}。

adj = {1: {2: 1, 3: 12}, 2: {4: 3, 3: 9}, 3: {5: 5}, 4: {3: 4, 5: 13, 6: 15}, 5: {6: 4}}

dis = {2: 1, 3: 12, 4: 666, 5: 666, 6: 666}

parents_node = {2: 1}

visited = []

min_dis = None

min_dis_point = None

for i in range(len(dis)):

sort_dis = sorted(dis.items(), key=lambda item: item[1])

# 找到dis中距离起始点距离最小的点

for p, d in sort_dis:

if p not in visited:

min_dis_point = p

min_dis = d

visited.append(p)

break

try:

# 更新相邻点的开销

for n in adj[min_dis_point].keys():

update = min_dis+adj[min_dis_point][n]

if dis[n] > update:

dis[n] = update

parents_node[n] = min_dis_point

except: # 最后一个点6有进没出,因此没有相邻点会报错

pass

travel = ''

for node, parents in parents_node.items():

travel += str(parents)+'->'+str(node)+'->'

print(dis)

print(travel)

从某种程度来说使用邻接表可能更容易理解些,由于不用使用索引,书写代码时也没那么容易出错,同时由于不用储存那些不相邻的节点权重,也即邻接矩阵中的的那些无穷大的值,因此也可以节约内存。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值