图的经典应用之一:最短路径

668 阅读2分钟

「这是我参与2022首次更文挑战的第28天,活动详情查看:2022首次更文挑战

图的经典应用之一:最短路径

生活中的许多问题都可以通过图来解决,比如:地图中两个城市之间的最短路径,如何通过最少的成本完成网络布线,工程上的进度控制等。

下面我们一起来了解下与获取最短路径相关的一些算法吧。

Dijkstra 算法

  • 基本思想
  • 步骤:
    • 数据结构
      • 【 节点u 到节点i 之间是否有边 —— 有边:G.Edge[u][i] = <u,i> 的权值;无边:= ∞ 】
      • 一维数组 dist[i](记录 —— 从原点u到节点i的最短路径长度)
      • 一维数组 p[i](记录 —— 最短路径上节点i的前驱)
    • 初始化
      • 集合 S = { u } + 集合
      • V-S 中的所有节点 i
      • dist[i] = G.Edge[u][i]
      • 有边 p[i] = u
      • 无边 p[i] = -1
    • 找最小
      • dist[t] = min
      • => 节点 t 为集合 V-S 中距离源点 u 最近的节点
    • 节点 t 加入集合 S + 更新集合 V-S
    • 判断是否结束 (集合 V-S 为空时结束)
    • 借东风-走捷径
      • 节点 t 的邻接点 j 可以通过节点 t 找到最小路径
      • dist[j] > dist[t] + G.Edge[t][j] => dist[j] = dist[t] + G.Edge[t][j]
      • 记录节点 j 的前驱 t( p[j] = t )

Floyd 算法

  • 别名:插点法
  • 核心:松弛操作【 两个节点间插入另一个节点,检查是否可以缩短两点间的距离 】
  • 步骤
    • 数据结构
      • 节点 i 到节点 j => 有边:G.Edge[i][j] = <i,j> 的权值;无边:= ∞
      • 辅助数组
        • 最短距离数组 dist[i][j] -> 记录两点间的最短路径长度
        • 前驱数组p[i][j] -> 记录两点间的最短路径上的前驱
    • 初始化(dist[i][j] = G.Edge[i][j])
      • 有边 p[i][j] = i
      • 无边 p[i][j] = -1
    • 插点
      • dist[i][j] > dist[i][k] + dist[k][j] => dist[i][j] = dist[i][k] + dist[k][j]
      • 记录节点前驱 p[i][j] = p[k][j]

Bellman-Ford 算法

  • 步骤
    • 数据结构【 边集数组 】(端点 a + b + 边权 w)
    • 松弛操作
    • 重复松弛操作 n-1 次
    • 判负环(多执行一次松弛操作,若仍可松弛,则有负环存在)

SPFA 算法

  • Bellman-Ford 的队列优化算法
  • 含有负权边的单源最小路径的求解 || 判负环
  • 最坏情况:时间复杂度同上 O(nm)
  • 稀疏图运行效率较高 O(km) (k << n)
  • 步骤
    • 创建队列【 源点 u 入队 + 标记 u 已经在队列中了 + u 的入队次数(+1) 】
    • 松弛操作
      • 取出队头 x + 标记 x 不在队列中了
      • 找出 x 的所有出边 i(x,v,w)
        • dis[v] > dis[x] + e[i].w => 松弛 dis[v] = dis[x] + e[i].w
        • v 不在队列中 -> 判断 (v 的入队次数 + 1) >= n ? (有负环 => 退出操作) : (v 入队 + 标记)
    • 重复上一步,至队列为空

图的使用场景还有很多,比如:最小生成树、拓扑排序等,对图比较感兴趣的小伙伴可以继续深入的探索