「这是我参与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 入队 + 标记)
- 重复上一步,至队列为空
结
图的使用场景还有很多,比如:最小生成树、拓扑排序等,对图比较感兴趣的小伙伴可以继续深入的探索