Go版数据结构 -【6.3 最短路径算法】

6.3 最短路径算法

在图结构中,最短路径算法 是一类非常重要的算法,它用于找到从一个顶点到另一个顶点的最短路径。

最短路径问题广泛应用于导航、通信网络、路径规划等领域。

最短路径问题 是在图中找到从起点到目标顶点的最短路径,路径长度是由边的权重决定的。对于不同类型的图,最短路径问题可以有多种解法。

最短路径问题的两种主要分类:

  • 单源最短路径:从一个源顶点出发,找到它到其他所有顶点的最短路径。

  • 全源最短路径:找到图中任意两个顶点之间的最短路径。

本节将介绍常见的几种最短路径算法,包括 迪克斯特拉算法(Dijkstra) 算法和 贝尔曼-福特(Bellman-Ford) 算法,并使用 Go 语言进行实现。

本节代码存放目录为 lesson14


狄克斯特拉算法

Dijkstra 算法 是一种用于解决单源最短路径问题的算法,适用于权重为正的图。

算法步骤如下所示:

  1. 初始化:将起点的距离设为 0,其他顶点的距离设为 (无穷大)。

  2. 对当前顶点的每个未访问的邻接顶点,计算其到起点的距离,更新邻接顶点的最短距离。

  3. 重复步骤 2,直到所有顶点都已访问或找到了目标顶点的最短路径。


算法示例如下所示:

我们以 A 为起点,寻找 A -> D 的最短路径。

图结构:

       (10)
    A ------ B
     \      / \
   (3)\  (1)  (2)
       \ /      \
        C        D
       /        /
      (4)     (6)
        \    /
           E
  1. 初始化:将起点 A 的距离设为 0,其他顶点的距离设为无穷大

    这个无穷大表示的是 A 到这个顶点的距离,由于目前还是未知的,所以设置为无穷大。

    随着计算往下进行,这个无穷大会逐渐变小,最终收敛为一个正确的数值。

    表示为:

     A: 0, B: ∞, C: ∞, D: ∞, E: ∞
    
  2. 第一步:从 A 出发,寻找可以直达的未访问顶点 BC

    • A -> B 的距离为 10

    • A -> C 的距离为 3

    因为 A -> C 的距离较短,因此选择 C 作为当前顶点,并更新最短距离为 3

    A: 0, B: 10, C: 3, D: ∞, E: ∞
    
  3. 第二步:从 C 出发,寻找可以直达的未访问顶点 BE

    • C -> B 的距离为 1,所以 A -> C -> B 的距离是 3 + 1 = 4(比原来的 A -> B 距离 10 更短)。

    • C -> E 的距离为 4,所以 A -> C -> E 的距离是 3 + 4 = 7

    更新距离 B 为更短的4E7

    A: 0, B: 4, C: 3, D: ∞, E: 7
    
  4. 第三步:从 B 出发,寻找可以直达的未访问顶点 D

    • B -> D 的距离为 2,所以 A -> C -> B -> D 的距离是 4 + 2 = 6

    更新距离 D6

    A: 0, B: 4, C: 3, D: 6, E: 7
    
  5. 终止:此时,D 的最短路径已经确定,A -> D 的最短路径为 A -> C -> B -> D,总距离为 6

    虽然 E 也可达,但 A -> E 的距离是 7,大于 A -> D6,因此最短路径为:

    A -> C -> B -> D
    

从上面我们可以看出,Dijkstra 算法会不断检查从起点到各个顶点的所有可能路径,逐步更新为更短的路径,直到找到最优解。

贝尔曼-福特算法

Bellman-Ford 算法 是另一种用于解决单源最短路径问题的算法,它能够处理带有负权重边的图,并且可以检测负权环。

它的核心思想就是:不断更新从起点到各个顶点的最短距离,直到所有可能的最短路径都被找到。

算法步骤如下所示:

  1. 初始化:设定起点的距离为 0,其他所有顶点的距离设为 (表示未知或非常远)。

  2. 松弛操作:对每条边进行检查,如果通过这条边能找到一条更短的路径,那就更新目标顶点的距离。

  3. 重复松弛:你要经过所有边 V-1 次(V 是顶点数量),确保每个顶点的最短路径都被找到。比如有 5 个顶点,那么就最少要经过 4 次。

  4. 检查负环:再经过一次所有边,如果还能继续更新路径,那就说明有负权环,即无穷循环的负数路径。


算法示例如下所示:

我们以 A 为七点,找到从 A 到每个顶点的最短路径。

图结构:

    A --(1)--> B
     \       /  \
   (4)\    (-2)  (3)
       \  /       \
        C --(1)--> D
  1. **初始化:**我们从起点 A 开始,所有点的初始距离如下:

    A: 0, B: ∞, C: ∞, D: ∞
    
  2. 松弛操作:
    我们检查每条边,更新路径。

    • A -> B 距离是 1,现在 B 的最短距离是 1
    A: 0, B: 1, C: ∞, D: ∞
    
    • A -> C 距离是 4,现在 C 的最短距离是 4
    A: 0, B: 1, C: 4, D: ∞
    
    • B -> C 距离是 -2,如果 A 通过 BC,距离是 1 + (-2) = -1,比 4 小,所以更新 C 的距离为 -1
    A: 0, B: 1, C: -1, D: ∞
    
    • B -> D 距离是 3A 通过 BD,距离是 1 + 3 = 4,更新 D 的距离为 4
    A: 0, B: 1, C: -1, D: 4
    
    • C -> D 距离是 1A 通过 CD 的距离是 -1 + 1 = 0,比 4 小,更新 D 的距离为 0

    更新后的状态:

    A: 0, B: 1, C: -1, D: 0
    
  3. **重复松弛:**重复步骤 2,共进行 V-1 次(这里是 3 次,因为有 4 个顶点)。经过 3 次松弛后,所有的距离都不会再更新了。

    在我们的这个例子中,后面 2 次松弛的结果都会是与上面相同的,因为已经都是最短的了。

  4. 检查负权环:

    再做一次松弛。如果这时还能更新任何距离,说明存在负权环,否则就没有负环。

那么狄克斯特拉与贝尔曼-福特的主要区别是什么呢?

  • Dijkstra 在对邻接顶点进行计算后,就不会再次计算,一旦更新了某个顶点的最短路径,就不会再重复计算。适用于边较多的图结构。

  • Bellman-Ford 会在每一轮松弛中都对所有边进行检查,哪怕已经得到了最优解,还是会继续执行完多次检查,所以它的执行次数是比 Dijkstra 要多的。适用于带有负权的图结构。

Go 语言的实现

Dijkstra 算法实现

图结构如下所示:

		(10)
    A ------ B
     \      / \
   (3)\  (1)  (2)
       \ /      \
        C        D
       /        /
      (4)     (6)
        \    /
           E

实现代码如下所示:

// Graph 定义图结构,使用邻接表表示
type Graph struct {
   
   
	vertices map[string]map[string]int
}

// NewGraph 创建一个新的图
func NewGraph() *Graph {
   
   
	return &Graph{
   
   
		vertices: mak
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Swxctx

您的鼓励将是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值