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
计算几何凸包旋转卡壳最近点对线段相交扫描线求交
查找二分查找二分边界旋转数组搜索二分答案三分查找

欧拉路径(Hierholzer 一笔画)

图算法 · 边走边消 · 图论的起点

从七桥问题说起

1736 年柯尼斯堡的市民想一次走遍七座桥、每座恰走一次——欧拉证明了不可能,图论就此诞生。 「每条边恰走一次」的路线叫欧拉路径(起终相同则叫欧拉回路), 也就是小学的一笔画。判定出奇地简单,只看度数:

连通图存在欧拉路径 ⟺ 奇度点恰 0 个(回路,随处起笔)或 2 个(路径,必须从一个奇度点起笔、终于另一个)。
直觉:中途路过一个点,总是「进一条边、出一条边」成对消耗——度数得是偶数;只有起点(只出不进一次)和终点(只进不出一次)允许奇数。七桥的四个点全是奇度,所以无解。

Hierholzer:走到卡住,弹栈接环

判定过了怎么把路线画出来?Hierholzer 用栈边走边消:沿未用过的边一路贪心走(走过就消掉、当前点压栈); 走到卡住(当前点无未用边)就弹栈、把它收进路径; 若弹完后栈顶还有余边,就从它接着走——走出的子环自动插进路径。 栈清空后把弹出序反转,就是欧拉路径,整个过程每条边恰进出栈一次,O(E)。 下图徽标是每个点的剩余未用边数——减到 0 就走不动;虚线环表示在栈上。

01234

一笔画:每条边恰好走一次,笔不离纸——能不能画完这 7 条边?这就是欧拉路径(起点终点可以不同;相同则叫欧拉回路)。Hierholzer 算法边走边消,O(E) 一次搞定

1function eulerPath(n: number, edges: [number, number][]): number[] {
2 const adj = Array.from({ length: n }, () => [] as { v: number; eid: number }[]);
3 edges.forEach(([u, v], i) => { adj[u].push({ v, eid: i }); adj[v].push({ v: u, eid: i }); });
4 const deg = adj.map((a) => a.length);
5 const odd = [...Array(n).keys()].filter((u) => deg[u] % 2 === 1); // 判定:奇度点 0/2
6 if (odd.length !== 0 && odd.length !== 2) return [];
7 const start = odd.length ? odd[0] : 0; // 有奇度点必须从它出发
8 const used = new Array(edges.length).fill(false);
9 const stack = [start];
10 const path: number[] = [];
11 while (stack.length) {
12 const u = stack[stack.length - 1];
13 const next = adj[u].find((e) => !used[e.eid]);
14 if (next) {
15 used[next.eid] = true; // 还有未用边:消边 + 压栈
16 stack.push(next.v);
17 } else {
18 stack.pop(); // 卡住:弹栈进路径
19 path.push(u);
20 }
21 }
22 return path.reverse(); // 反转弹出序即欧拉路径
23}
栈(空)
路径(空)
目标7 条边各走一次
1 / 12

为什么子环插得进去

第一段路在终点卡住时,剩下没走的边(如果有)一定挂在栈上某个点的周围, 而且这些残边每个点的度数都是偶数(进出成对)——所以从栈顶那个有余边的点出发, 必然能走一个环回到它自己。弹栈的顺序恰好把这个环「缝」在正确的位置。 应用远不止一笔画:DNA 测序把读段重叠建成 de Bruijn 图后找欧拉路径拼接基因组; 扫雪车/邮递员要走遍每条街,是它的加权推广(中国邮递员问题)。与 强连通分量、LCA 一样,都是「一遍 DFS/栈」榨干图结构的经典。