最短路之Dijkstra算法——以不同城市之间的距离为例(基于python)

        首先,非常感谢b站up主对于Dijkstra算法的介绍,受益匪浅,关于这个算法的视频链接在这[Python学习]实现迪杰斯特拉算法并生成最短路径。我也是跟着这位up主才算慢慢懂了这个算法的具体情况。下面,本文就关于Dijksta算法进行相关解释以及python代码实现和案例分析。

目录

一、背景

二、 代码及解释

三、 第三方库解决方法

四、 案例——设备更新问题

五、 总结


一、背景

        最短路算法不仅可以解决任意两个地点之间的最短路径,还可以解决诸如管道铺设、线路安全、工厂布局、设备更新等问题。

        这里就不讲关于图论的一些东西,讲起来有点复杂,在能理解本文的基础上,给大家先说一下如何去理解下面这个图

        可以看到,图上有些点是直接连接的,有些是没有。对于直连的,如A到B,线上有标注2,即为A到B的距离,但对于A到C,这种没有直接连接的点,记它的距离为无穷大。节点即图上的点。本文使用的例图为无向图,即表示可以从A到B,也可以从B到A。对于有向图的即表示只能顺着箭头方向走,不能往回走,距离也就为无穷大。

        本文主要讲最短路算法中的Dijksta算法,它适用于路权非负的情况。具体步骤简化理解大概就是(个人理解,如有误,欢迎大佬批评指正):

        1.先确定起始点,起始点与起始点之间的距离为0

        2.确定终点,找出起始点能到达终点的全部路径

        3.通过迭代,找出起始点到终点的最短距离,并对它进行标记

        乍一看,很简单,然而,并不然,如果起始点与终点之间存在很多中间点时,就很难通过人工计算。下面,通过python去实现诸如上述三个步骤。

二、 代码及解释

       以上图为例子, 我们输入的矩阵就是network,也就是常说的邻接矩阵,这个矩阵为方阵。第一行第一列的数据为A到自A的距离。但里面的零并不一定表示自己到自己的距离,例如第一行第五列,表示A到E是没有直连的,这可能跟我上面说的无穷有些矛盾,不过算法真正运行的不是这个矩阵,所以先不用管。仔细观察,可以发现这个矩阵是对称的,当然,也仅限于无向图。

        定义函数,s为起始点,d为终点

        path是一个空列表,用于储存从某点到某点的最短路径,从这里看出s到d的最短路径需要经过哪些点。

        n为节点个数。

        对于fmax,引用numpy库中的inf,意味无穷大。

        w为构造的与输入矩阵network同样维数的0矩阵,用于算法运行。

        book为一个列表,元素个数为节点数,它的作用是,当已经确定某点到某点的最小距离时,进行标记,若有其他路径包括这条路径,即可减少程序的计算。

        dis为最终返回的路径列表,表示从某点到某点的最小路径,一共有n个。

        minpath存储某点到某点的中会经过的节点。path中的节点由它决定。

import numpy as np


network = np.array([[0,2,4,0,0,0,0],
                 [2,0,0,3,3,1,0],
                 [4,0,0,2,3,1,0],
                 [0,3,2,0,0,0,1],
                 [0,3,3,0,0,0,3],
                 [0,1,1,0,0,0,4],
                 [0,0,0,1,3,4,0]])

def Dijkstra(network,s,d):  # 迪杰斯特拉算法算s到t的最短路径,并返回该路径和距离
    print("Start Dijstra Path……")
    path = []  # 储存s到d的最短路径
    n = len(network)  # 节点个数
    fmax = np.inf
    w = [[0 for i in range(n)] for j in range(n)]
    book = [0 for i in range(n)]  # 是否已经是最小的标记列表
    dis = [fmax for i in range(n)]  # s到其他节点的最小距离
    book[s-1] = 1  # 节点编号从1开始,列表序号从0开始
    midpath = [-1 for i in range(n)]  # 上一跳列表


         这一步就是将w矩阵初始化,变成程序能够处理的标准矩阵,处理后的w中直连或者本身为fmax。对于i=s-1,其实是因为列表的序号是从0开始的,而我们习惯是从1开始

'''这个循环把原矩阵中的0标价为fmax,且认为直连就是最短(但事实并非如此)'''
    for i in range(n):
        for j in range(n):
            if network[i][j] != 0:
                w[i][j] = network[i][j]  # 0→max
            else:
                w[i][j] = fmax

            if i == s-1 and network[i][j] != 0:  # 直连的节点最小距离就是network[i][j]
                dis[j] = network[i][j]

        接下来就是算法的主体部分,n-1次循环就是除了s的节点外的其他节点,for循环中第一个min是会不断更新的,直到找到最短距离,它的作用即在下方的if判断中,检查是不是距离是不是小于min,如果是的话,对min进行更新,否则用u进行记录,标记出已经找出最小路径的点。dis最初的元素全为fmax,通过迭代更新其中的元素,此时通过这个for循环得到的还不是最小的,要在经过下一个循环。不过,最终得到的都是最小路径。例如dis输出为[inf, 2, 4, 5, 5, 3, 6],即表示从第一个点出发,依次到各个点的最小距离。

        下一个以v为元素遍历的for循环中,这一步用于比较直连是否是最短,如果不是,则换其他的,同时对minpath列表进行更新。此时最短距离已经更新完毕,接下来是得到路径。

        j=d-1的解释和上面的i一样,都是由于习惯不同。

 '''算法主体'''
    for i in range(n-1):  # n-1次遍历,除了s节点
        min = fmax  # min的值是一直变化的
        for j in range(n):
            if book[j] == 0 and dis[j] < min:  # 如果未遍历且距离最小,反复比较得出最短距离
                min = dis[j]
                u = j
        book[u] = 1  # 标记已到达的节点
        # u处于一个循环体内,从2到7遍历
        for v in range(1,n):  # u直连的节点遍历一遍
            '''关键步骤,用于比较与此节点直连的节点的长度'''
            if dis[v] > dis[u] + w[u][v]:
                dis[v] = dis[u] + w[u][v]
                midpath[v] = u+1  # 上一跳更新
    j = d-1  # j是序号
    path.append(d)  # 因为存储的是上一跳,所以先加入目的节点d,最后倒置

        在此循环中,只是s到d的最短路径,并不包括其他点之间。

        对于后面那个reverse方法,从上面path添加d就可以知道,需要转置,要不然就是反的

 while midpath[j] != -1:
        path.append(midpath[j])
        j = midpath[j]-1
    path.append(s)
    path.reverse()  # 倒置列表
    print('path:',path)
    print(dis)

        至此,算法也就完成了,接下来,调用这个函数就行,试验用A到G的最短路,即1到7.

Dijkstra(network,1,7)

        结果表示,从A到G的最短距离为6,路径为A—B—D—G。整个算法想要完整理解还是稍微有点困难(大佬避开嘿嘿),大家可以反复观看以下,以下给出完整得代码,便于观察。

import numpy as np


def Dijkstra(network,s,d):  # 迪杰斯特拉算法算s到t的最短路径,并返回该路径和代价
    print("Start Dijstra Path……")
    path = []  # 储存s到t的最短路径
    n = len(network)  # 节点个数
    fmax = np.inf
    w = [[0 for i in range(n)] for j in range(n)]
    book = [0 for i in range(n)]  # 是否已经是最小的标记列表
    dis = [fmax for i in range(n)]  # s到其他节点的最小距离
    book[s-1] = 1  # 节点编号从1开始,列表序号从0开始
    midpath = [-1 for i in range(n)]  # 上一跳列表

    # 此步可以省略,只要输入矩阵规范就可
    '''这个循环把原矩阵中的0标价为fmax,且认为直连就是最短(但事实并非如此)'''
    for i in range(n):
        for j in range(n):
            if network[i][j] != 0:
                w[i][j] = network[i][j]  # 0→max
            else:
                w[i][j] = fmax

            if i == s-1 and network[i][j] != 0:  # 直连的节点最小距离就是network[i][j]
                dis[j] = network[i][j]

    # print('dis:',dis)
    '''算法主体'''
    for i in range(n-1):  # n-1次遍历,除了s节点
        min = fmax  # min的值是一直变化的
        for j in range(n):
            if book[j] == 0 and dis[j] < min:  # 如果未遍历且距离最小,反复比较得出最短距离
                min = dis[j]
                u = j
        # print('book:',book)
        book[u] = 1  # 标记已到达的节点
        # print('u:',u)
        # print('dis:',dis)
        # u处于一个循环体内,从2到7遍历
        for v in range(1,n):  # u直连的节点遍历一遍
            '''关键步骤,用于比较与此节点直连的节点的长度'''
            if dis[v] > dis[u] + w[u][v]:
                dis[v] = dis[u] + w[u][v]
                midpath[v] = u+1  # 上一跳更新
    j = d-1  # j是序号
    path.append(d)  # 因为存储的是上一跳,所以先加入目的节点d,最后倒置

    while midpath[j] != -1:
        path.append(midpath[j])
        j = midpath[j]-1
    path.append(s)
    path.reverse()  # 倒置列表
    print('path:',path)
    print(midpath)
    print(dis)

network = np.array([[0,2,4,0,0,0,0],
                 [2,0,0,3,3,1,0],
                 [4,0,0,2,3,1,0],
                 [0,3,2,0,0,0,1],
                 [0,3,3,0,0,0,3],
                 [0,1,1,0,0,0,4],
                 [0,0,0,1,3,4,0]])
Dijkstra(network,1,7)

三、 第三方库解决方法

        如果大家实在看不懂上面的代码,但又想用这个方法,这边给出了第三方库的解决方案,使用networkx库,由于对这个库了解不是很深,目前我只知道这个库可以画这类图,以及用Dijkstra算法求最短路,最大流问题。接下来就用代码,跟大家介绍以下这个方法

import networkx as nx

dis = [(0,1,2),(0,2,4),(1,3,3),(1,4,3),(1,5,1),(2,3,2),(2,4,3),(2,5,1),(3,6,1),
       (4,6,3),(5,6,4)]
g = nx.Graph()
g.add_weighted_edges_from(dis)
a = nx.to_numpy_matrix(g,nodelist=range(7))
p = nx.dijkstra_path(g,source=0,target=6,weight='weight')
d = nx.dijkstra_path_length(g,0,6,weight='weight')
print('最短路径为p={},最短距离为d={}'.format(p,d))

        同样是用上面的例子,但是对比之下,代码就少了很多。简单介绍一下。

        第一个dis是一个列表,(0,1,2)表示从A到B的距离为2,,以此类推。

        g为初始化一个图,Graph表示生成无向图。

        接下来为图添加节点和路权,参数为dis

        a对应生成的邻接矩阵。

        p使用Dijkstra算法得到最短路径,d得到最短距离。

         可以看出,和上面的代码算法得到结果一致,不过要看那个p列表,可以全部元素+1,即和上面的代码运行结果一致。

四、 案例——设备更新问题

        接下来用一道题实践一下,虽然跟直接说某点到某点之间的最短路径不同,但是本质是一样的。不过这题我也只是试了一下,不一定保证对,所以大家可以思考一下,希望能对大家有所启发。

        某企业在今后5年内需使用一台机器,该种机器的每年购置费和维修费(与机器的役龄有关)如下表所示。试制定机器购置和更新计划,使5年内的成本最小。

年份

1

2

3

4

5

购置费

维修费

11

5

11

6

12

8

12

11

13

18

        我的想法是这样的:

        令w_{ij}表示第i年初买进用到第j-1年末中所包含的所有费用,此时wij即包含第i年买进时的购置费和j-i年的维修费用,故可知

w_{16}=59,w_{15}=41,w_{13}=22,w_{12}=16,w_{23}=16,w_{22}=24, w_{25}=30,w_{26}=41,w_{34}=17,w_{35}=23,w_{36}=31,w_{45}=17, w_{46}=28,w_{56}=18

        用networkx库画出对应的图,如下所示:

         最终用代码解得,第一年买进用到第三年,然后在第三年买入用到第五年末,可使费用最低,为53单位。下面为代码

import networkx as nx
import numpy as np
import pylab as plt


dis = [(1,5,41),(1,4,30),(1,3,22),(1,2,16),(1,6,59),(2,3,16),
       (2,4,23),(2,5,30),(2,6,41),(3,4,17),(3,5,23),(3,6,31),
       (4,5,17),(4,6,28),(5,6,18)]
g = nx.DiGraph()
g.add_nodes_from(range(1,7))
g.add_weighted_edges_from(dis)
pos = nx.shell_layout(g)
w = nx.get_edge_attributes(g,'weight')
nx.draw(g,pos,with_labels=True,font_weight='bold',font_size=12)
nx.draw_networkx_edge_labels(g,pos,edge_labels=w)
plt.show()
a = nx.to_numpy_matrix(g,nodelist=range(1,7))
p = nx.dijkstra_path(g,source=1,target=6,weight='weight')
d = nx.dijkstra_path_length(g,1,6,weight='weight')
print(p,d,sep='\n')

        g.add_nodes_from表示增加节点,g.add_weighted_edges_from表示增加路权,参数位dis,pos为作图的一个参数,代码从w开始到plt.show都是为作图环节(如不需要,可以忽略)。接下来和上面示例一致,用Dijkstra算法解决。

五、 总结

        使用算法一步一步算还是比较困难的,所谓算法就是给人想的,程序是代码想的,关键就是如何让电脑理解你的想法,不过这个Dijkstra算法有现成的库还是比较方便的,当然也不要只是局限于求某点到某点的最短距离,可以引申一下,达到真正理解。

        算法,还是需要多练。对于上面的代码和算法的解释,如果有错误的地方,欢迎各位大神批评指正。有不懂的地方,也可以评论区或者直接私信我。

        创作不易,谢谢点赞——收藏——关注!!!

  • 7
    点赞
  • 48
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
遗传算法是一种通过模拟自然进化过程搜索最优解的方法。它模仿生物的遗传进化原理,通过选择、交叉与变异等操作机制,使种群中个体的适应性不断提高,最终达到最优。在解决最短路径问题中,可以使用遗传算法来寻找最短路。引用中提到了一种基于路径表示的编码方法,在该方法中,遗传算法的选择操作根据个体的适应度值来选择个体作为下一代的父代,交叉操作采用部分匹配交叉,变异操作则通过在个体的编码中交换两个城市位置来实现。可以根据个体的适应度值不断迭代,直到找到满足最短路径的解。同时,引用中提到当城市数量较多时,可能会出现陷入局部最优解的问题,可以考虑改进遗传算法来跳出局部最优解,如采用类似于粒子群优化或蚁群算法的思想来改进算法。总之,遗传算法是一种有效的求解最短路径问题的方法,可以使用MATLAB等工具来实现该算法。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [遗传算法解决TSP问题MATLAB实现(详细)](https://blog.csdn.net/xyisv/article/details/86741983)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [遗传算法——matlab代码解析](https://blog.csdn.net/weixin_36162883/article/details/115946521)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值