Dijkstra算法
它解决了什么问题
在大多数最短路径问题中,Dijkstra算法是最常用的、效率最高的。它是一种“单源”最短路径的算法,一次计算能够得到从一个起点到其他所有点的最短距离长度、最短路径的途径点
输入为带权图G=(V,E),边权非负(有负权边考虑Bellman-Ford/SPFA/Johnson),给定起点,适用于有向图和无向图(无向图就加双向边)
核心思想(贪心+松弛)
维护每个点的“目前已知最短距离”dist[],反复从未确定的点里选出dist最小者u(用最小堆加速),将它永久确定;然后用u去尝试”放松”所有出边(u,v,w):
如果dist[v]>dist[u]+w,就更新之,并记录parent[v]=u以便还原路径
因为所有边权非负,当前dist最小的未确定点u再走任何边其代价只会增加,不可能通过未确定的其他点得到更短的代替路径,故一旦选出就最优(贪心选择性质)
实现细节(邻接表+最小堆)
- 数据结构:
adj[u]存(v,w),priority_queue堆按(dist,node)升序 - 常量:
INF取很大(如4e18),避免溢出 - 复杂度:用二叉堆:、用Fibonacci堆(理论):、稠密图用邻接矩阵+线性扫描:更简单
步骤示例
| 步骤 | 做法 | 具体操作 | 结果 |
|---|---|---|---|
| 1 | 从起点s出发,用BFS扩展它的邻居节点 | 把这些邻居点放到一个集合A中,并记录这些点到s的距离 | |
| 2 | 选择距离s最近的邻居v,继续用BFS扩展v的邻居 | 1. 在A中找到距离s最小的点v,把v的邻居点放到A中 2. 如果v的邻居经过v中转,到s的距离更短,则更新这些邻居到s的距离 3.从集合A中移走v,后面不再处理v | 1.得到了从s到v的最短路径 2.v的邻居更新了到s的距离 |
| 3 | 重复步骤2,直到所有点都扩展到并计算完毕 | 集合A为空,计算出所有点到s的最短距离 |
C++模板(邻接表+最小堆,含路径还原)
1 |
|
用
long long存边权与距离;du!=dist[u]过滤过期项
多组数据时记得清空容器与重置dist/Parent
无向图要加双向边,有多条边时正常处理(选择更优)
Go模板(最小堆+路径还原)
1 | package main |
稠密图的O()写法
适合
dist[s]=0,其余INF全false循环
n次:- 在线性扫描访问点中找
dist最小的u,标记vis[u]=true - 用u松弛所有
v:dist[v]=min(dist[v],dist[u]+w[u][v])复杂度
- 在线性扫描访问点中找
常见坑
- 负权/负环:dijkstra不适用,哪怕只有一条负边也不行
- 整型溢出:总全值可能很大,统一用64位整型
- 多组数据:注意初始化
- 不可达:输出-1等条件
- 双向边别忘了加两次:并行多边/自环无序特判,不会影响正确性
进阶与变体
- 0-1 BFS:边权只取0/1,用双端队列
- Dial’s Algorithm:非负小整数权(最大权C不大)可用桶实现
- 多源最短路:把所有源点
dist=0同时入堆 - A*:有启发式的单源单目标更快,需要一致,可采纳的启发函数
- K次最短路:在Dijkstra框架上记录到达次数
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 JasmineRain's blog!
评论
