• 最短路径算法之一:单源无权图,python实现


    在图论中,有一类经典问题,就是最短路径问题。最短路径问题,又分为:

    • 单源无权图最短路径问题
    • 单源有权图最短路径问题
    • 多源有权图最短路径问题

    问题:啥叫单源,多源?

    单源指的是给定一个起点,求到任意顶点的最短路径;多源指的是给定两个顶点,求这两个顶点的最短路径。

    问题:啥叫有权,无权?

    权,即权重,指的是两个邻接点的边的权重。无权的条件下,我们默认所有边的权重一致(给个默认值);有权条件下,每条边的权重不一致。这就导致了V1到V2也许经过的顶点数不是最少的,但可能是路径最短的,比如下图:

    v1-v3经过的顶点最少,但路径不是最短的,最短路径是v1-v2-v3。

    这篇文章,讨论单源无权图的最短路径,以下图为例展开讨论。

    算法思想:

            利用BFS广度优先算法,把路径按照递增的顺序入队,对第k个顶点,假设他的最短路是n,那么对k的(未访问过的)邻接节点来说,它们的最短路则是n+1,所以满足BFS满足路径递增要求。用一个dist 数组保存s到各顶点的最短路径。dist[i] 表示 从顶点s到i 的最短路径,默认为-1。

            先把起点s压入队列,dist[s] = 0(自己到自己);

            从队列中弹出一个顶点V,直到队列为空;遍历该顶点的所有未经访问的邻接结点(未经访问的结点dist[i] = -1,否则为最短路径),并更新邻接结点的dist为 dist[V]+1;

            当队列为空时,已经求得s到任意顶点的最短路径。

    步骤拆分分析:

    先确认起点s,假设起点是V1,V1入队。

    • 此时队列 [ v1 ]

    从队列中弹出一个顶点:V1,访问V1的所有邻接点。

    • V1的邻接点有V2、V4。V2、V4都没有访问过,更新他们的dist为dist[V1] + 1,并依次入队
    • 此时队列 [ v2, v4 ]

    从队列中弹出一个顶点:V2,V2的所有邻接结点的最短路径是 dist[V2]的基础上再加一条边,符合按照路径递增的顺序访问,先访问dist = 1的顶点,然后访问dist=2的顶点,.....。

    • V2的邻接结点有V4、V5,由于V4已经访问过了,跳过V4。更新dist[V5] = dist[V2]+1,V5入队。
    • 此时队列 [ v4, V5 ]

    从队列中再弹出一个顶点:V4,接着跟上次的步骤一样。

    • V4的邻接结点有V3、V5、V6、V7,由于dist[V5]不等于-1,表示已经访问过了,跳过V5。更新dist[V3/6/7] = dist[V4]+1,V3、V6、V7入队。
    • 此时队列 [v5, v3, v6, v7]

    从队列中再弹出一个顶点:V5,接着跟上次的步骤一样。

    • V5的邻接结点有V7,但是由于V7已经访问过了,所以跳过
    • 此时队列 [v3, v6, v7]

    从队列中再弹出一个顶点:V3,接着跟上次的步骤一样。

    • V3的邻接结点有V1、V6,但是由于V1和V6都访问过了,所以跳过
    • 此时队列 [v6, v7]

    从队列中再弹出一个顶点:V6,接着跟上次的步骤一样。

    • V6的没有邻接结点,跳过
    • 此时队列 [v7]

    从队列中再弹出一个顶点:V7,接着跟上次的步骤一样。

    • V7的邻接结点有V6,由于V6已经访问过了,跳过
    • 此时队列 [],为空

    此时判断队列为空,说明所有结点已经求得最短路径。

    代码实现:

    1. class UnweightBFS(BFSVisit):
    2. def __init__(self):
    3. super(UnweightBFS, self).__init__()
    4. self.dist = []
    5. def initVisted(self, g):
    6. super(UnweightBFS, self).initVisted(g)
    7. self.dist = [-1 for v in range(g.nV)] # 保存s到i的最短路径
    8. def visit(self, g, v: int):
    9. """
    10. 最短路径
    11. g: 图(矩阵实现)
    12. v: 起点
    13. """
    14. Q = QueueArr() # 初始化一个空队列 (我自己写的队列)
    15. Q.addQ(v)
    16. self.dist[v] = 0
    17. while(not Q.isEmpty()):
    18. V = Q.deleteQ()
    19. for i, val in enumerate(g.G[V]):
    20. if (self.dist[i] == -1 and g.G[V][i] > 0): # 未访问过,且是邻接节点(有边)
    21. self.dist[i] = self.dist[V] + 1
    22. Q.addQ(i)

  • 相关阅读:
    反沙箱方法
    方案:基于AI烟火识别与视频技术的秸秆焚烧智能化监控预警方案
    (完美方案)解决mfc140u.dll文件丢失问题,快速且有效的修复
    后仿真中的 《specify/endspecify block》之(5)使用specify进行时序仿真
    Scala 基础 (三):运算符和流程控制
    FFmpeg源码走读之内存管理模型
    UVA220 黑白棋 Othello
    【教3妹学编程-算法题】逃离火灾
    Java笔试复盘
    哪里能找到可以学习的前端实战项目?
  • 原文地址:https://blog.csdn.net/weixin_40647516/article/details/126270240