V

算法可视化

ZHEN
分享到微博分享到 XGitHub 仓库个人主页
学习工具算法复杂度速查算法学习路径
数据结构数组链表栈队列树堆哈希表图字典树并查集LRU 缓存跳表线段树B+ 树布隆过滤器树状数组
经典排序算法冒泡排序鸡尾酒排序双调排序选择排序插入排序二分插入排序希尔排序归并排序自顶向下归并快速排序三路快排双轴快排堆排序计数排序基数排序桶排序
图算法Dijkstra 最短路Kruskal 最小生成树Prim 最小生成树Bellman-Ford 最短路拓扑排序Floyd 多源最短路强连通分量2-SAT最大流二分图匹配LCA 倍增欧拉路径
动态规划编辑距离0-1 背包完全背包最长公共子序列最长递增子序列硬币找零方案数石子合并旅行商 TSP树形 DP数位 DP换根 DP
回溯与搜索N 皇后子集生成全排列组合总和迷宫寻路岛屿数量单词搜索数独A* 寻路
字符串KMP 字符串匹配Rabin-KarpBoyer-MooreManacher后缀数组LCP / height 数组AC 自动机Z 函数
数学与数论埃氏筛线性筛欧几里得算法快速幂扩展欧几里得中国剩余定理欧拉函数米勒-拉宾FFTPollard's Rho
计算几何凸包旋转卡壳最近点对线段相交扫描线求交
查找二分查找二分边界旋转数组搜索二分答案三分查找

Dijkstra 最短路

图算法 · 单源最短路径

什么是最短路

带权图里,从一个起点到各个点的「最短总距离」是多少、怎么走?逐条路径去试不现实。Dijkstra 给了高效解法——前提是边权非负。

Dijkstra 怎么做

维护一张「源到各点的当前最短距离」表(源点为 0,其余为 ∞)。然后反复做两步:①取出还没定下来、当前距离最小的点,把它「定下来」;②用它去松弛(relax)邻边——如果「经它中转」到某个邻居更短,就更新那个邻居的距离。每个点被定下来时,它的距离就是最终最短距离。

下面固定一张 6 个点的带权有向图(源 A)。点「下一步」逐步看它怎么取当前最近点(琥珀高亮)、确定它(变绿)、松弛邻边(黄边+ 节点旁距离徽标更新)——特别留意 B、D、E、F 的距离会被更短的路径反复降低(先到的不一定最短);走到底会点亮整棵最短路树(绿边)。右侧代码随每一步同步高亮。

412517362A0B∞C∞D∞E∞F∞

源 A 距离置 0,其余置 ∞

1function dijkstra(adj: [number, number][][], source: number, n: number): number[] {
2 const dist = Array(n).fill(Infinity);
3 dist[source] = 0;
4 const done = Array(n).fill(false);
5 for (let k = 0; k < n; k++) {
6 let u = -1;
7 for (let i = 0; i < n; i++)
8 if (!done[i] && dist[i] < Infinity && (u < 0 || dist[i] < dist[u])) u = i;
9 if (u < 0) break;
10 done[u] = true;
11 for (const [v, w] of adj[u]) {
12 if (dist[u] + w < dist[v]) {
13 dist[v] = dist[u] + w;
14 }
15 }
16 }
17 return dist;
18}
n6
k0
u(当前点)-
已确定-
dist[A]0
dist[B]∞
dist[C]∞
dist[D]∞
dist[E]∞
dist[F]∞
1 / 32

为什么「贪心地取当前最近点」是对的?因为边权非负,一个已经定下来的点,它的距离不可能再被后面更小——后面的路只会更长。用二叉堆做优先队列,复杂度约 O((V + E) log V)。注意它不能处理负权边(那要用 Bellman-Ford)。

Dijkstra 在哪里用

地图 / 导航:求两地最短路线。
网络路由:如 OSPF 协议按链路开销选路。
任何「非负权图上的单源最短路」。

它是图算法这条线的开篇——后面还有 Bellman-Ford(带负权)、Floyd(多源最短路)、最小生成树、拓扑排序等。想先复习图本身怎么存、怎么遍历,可回看 数据结构 · 图。