清晨,古镇邮递员小王摊开全镇街道图,想设计一条送信路线:不重复地走完每一条街道,最后还能回到邮局。他越看越觉得,这跟小时候玩的一笔画游戏一模一样——笔尖不离开纸、不重复描任何一条线,看能不能一笔画完全图。两百多年前,欧拉就是被柯尼斯堡的七座桥问题勾起好奇,最终建立了欧拉路径与欧拉回路的完整理论。这篇文章会沿着小王的邮递路线,带你认识图、顶点、度数、奇度顶点,学会用 Hierholzer 算法拆环拼接、用 Fleury 算法过桥避让,还会看到同样的思路如何拼接 DNA、规划机器人路径。你会发现,一个看似游戏的数学问题,其实是计算机算法世界里最常见的地图。
对 AI 从业者而言,欧拉路径是图论算法的基础武器,理解它,你才真正掌握了路径规划、序列组装、物流优化等问题的底层思维。
目录
可视化演示
欧拉路径和欧拉回路是经过每条边恰好一次的路径和回路,Hierholzer算法可高效求解。本文讲解欧拉路径与回路... 切换模式查看不同维度。
本节配有交互动效(进入视口后自动加载,约 3KB)。
欧拉路径与回:核心原理 动态展示关键流程与数据流动 交互控件调节参数实时渲染 f(x) = transform(x) 核心概念的可视化呈现
基本概念:路口的度与一笔画
清晨,小王摊开古镇街道图,他要找一条路线,每条街只走一次,最后回到邮局。他发现,每个路口都连着几条街,有的路口连着三条街,有的连着两条。这个“连着几条街”的数字,正是图论里最朴素的度量——度。小王的直觉是:能不能一笔画,和这些度有很大关系。
把地图抽象成图 \(G=(V,E)\),\(V\) 是路口集合,\(E\) 是街道集合。一段从某个路口出发、沿着街道走到另一个路口的连续轨迹,如果每条街道恰好被经过一次,就称为欧拉路径;如果还能回到出发路口,就是欧拉回路。这里的关键不是顶点出现几次,而是边不重复。
小王恍然大悟:他在意的是每一条街,而不是每个路口。欧拉路径,就是“每条街道恰好走过一次”的行走方案。
- 交替序列\(W\) 表示一条行走轨迹,顶点和边交替出现,从路口 \(v_0\) 出发,经街道 \(e_1\) 到达 \(v_1\)。
- 边不重复要求 \(e_1, e_2, \dots, e_k\) 两两不同,这是欧拉路径最核心的约束。
- 回路条件如果起点与终点重合,即 \(v_0 = v_k\),就形成一条欧拉回路。
判定条件:连通是第一道门槛
小王发现,古镇被一条河隔成两个片区,两片之间没有桥。就算每个路口度数都正常,他也没法从东岸走到西岸。所以,判断一笔画之前,首先要确认地图是“连通的”——所有含街道的路口必须在一个连通块内,孤零零的空院子可以忽略不计。
在无向图中,每一条街道都会给两个端点的度各加 1。把所有路口的度加起来,恰好等于边数的两倍,这就是握手定理。于是,一个连通无向图存在欧拉回路,当且仅当每个顶点的度都是偶数。这个判据简洁又优雅,是小王判断自己能不能回邮局的黄金法则。
小王的结论是:先看地图是否连通,再数每个路口的街道数是否为偶数。两个条件都满足,他就能不重复地走完所有街道,回到邮局。
- 度数之和\(\sum_{v \in V} \deg(v)\) 把所有路口连出的街道数加起来,表示总的“街道端点”数量。
- 两倍边数每条街道有两个端点,因此总度数必然等于 \(2|E|\)。
- 偶度条件\(\forall v \in V,\ \deg(v) \equiv 0 \pmod 2\) 表示每个路口的度数都能被 2 整除,这样进入一个路口总能找到另一条街离开。
奇度顶点:入口和出口的秘密
小王继续研究,发现有些路口特别“别扭”:它们连着的街道数是奇数。比如一个三岔路口,无论从哪个方向进来,想要离开时总会发现少一条街。这类奇度顶点,就像一扇只能进不能出(或只能出不能进)的门。
图论里的判定规则更精确:如果一个连通图有 0 个奇度顶点,那么存在欧拉回路;如果有恰好 2 个奇度顶点,那么存在欧拉路径,而且这两个奇度顶点恰好是起点和终点;如果奇度顶点多于 2 个,则不存在欧拉路径,一笔画无解。
小王拿着地图数了一圈:如果只有两个“别扭”的路口,那就从一个奇度路口出发,从另一个奇度路口收工,正好完成一笔画。这条规律,是他规划邮路的第一张决策表。
- 奇度集合\(\{v \in V : \deg(v) \equiv 1 \pmod 2\}\) 表示所有度数为奇数的顶点集合。
- 集合大小外层 \(\bigl|\cdot\bigr|\) 统计这个集合里有多少个顶点,也就是奇度顶点的数量。
- 结果分支数量为 0 时存在欧拉回路;数量为 2 时存在欧拉路径,且起点和终点分别是这两个奇度顶点。
Hierholzer算法:拆环再拼项链
小王发现,古镇的街道网很像一串项链:由好几个小环在路口处互相串在一起。他灵机一动,只要先走完一个环回到老地方,再插到另一个环里继续走,最后把所有环串成一条完整路线,不就完成了?这就是 Hierholzer 算法的核心思想——拆环、走环、拼环。
算法具体怎么做?从起点出发,选择一条还没走过的边就走,走完立刻删掉。走到某个路口发现无路可走时,就把这个路口压入栈中并回退,直到遇到一个还有未走边的路口,再从那里出发找新的子环。最终所有子环在公共顶点处拼接成完整欧拉回路。整个过程每条边只处理一次,时间复杂度为 \(O(E)\)。
小王把地图上的街道网拆成一堆小铁环,再一个一个串起来。Hierholzer 的栈,正是他手里那根穿项链的线。
- 子回路分解\(C_1, C_2, \dots, C_k\) 是从图中拆出的一个个子回路,每个子回路都是一段闭合环。
- 拼接运算\(\oplus\) 表示两个子回路在公共顶点处穿插拼接,形成更大的回路。
- 边数守恒所有子回路的边数之和等于原图总边数,说明拼接时没有遗漏任何一条街。
Fleury算法:过桥前先看看能不能回来
小王走着走着,遇到了一座独木桥。他很清楚,如果提前过了这座桥,桥对岸的街道没走完,他就再也回不来了。所以每次选择下一条街道前,他都会问:这条街是不是桥?如果是桥,就尽量留到没有别的路可走时再过。
图论中,桥(割边)是指删除后会让图变得不连通的边。Fleury 算法每一步都优先选择非桥边,只有在没有选择时才过桥,从而保证剩余街道始终连通,最终一定能走完欧拉路径。代价是每一步都要判断桥边,算法复杂度为 \(O(E^2)\)。
小王把独木桥当作最后的退路,只要还能绕路,就绝不提前过桥。Fleury 的每次犹豫,都是对“回头路”的珍惜。
- 删边后的图\(G - e\) 表示从原图中移除边 \(e\) 后得到的新图。
- 连通分量数\(c(\cdot)\) 统计图中的连通分量个数,\(c(G)\) 是原图的连通分量数。
- 桥的判据如果删掉 \(e\) 后连通分量数变多,即 \(c(G - e) > c(G)\),说明 \(e\) 是桥。
欧拉与哈密顿:走遍每条街 vs 走遍每间屋
小王后来听说,还有一个问题叫“旅行商问题”,要求访问镇上的每一户人家。他觉得这和自己的送信工作很像,但仔细一想又完全不同:邮递员要走的是一条条街道,而推销员要访问的是一户户人家。前者对应欧拉回路,后者对应哈密顿回路。
欧拉回路要求走遍图中每条边恰好一次;哈密顿回路要求经过每个顶点恰好一次。判定的难度也天差地别:欧拉回路只需检查度数和连通性,是多项式时间;哈密顿回路则是最著名的 NP 完全问题之一,至今没有快速算法。
小王终于明白,邮局和旅行社是两本账。想判断能不能回邮局,数街道;想走遍每个村庄,可能要算到天荒地老。
- 欧拉边数欧拉回路 \(C_{\mathrm{euler}}\) 的边数等于原图边数 \(|E|\),因为它用过每条边一次。
- 哈密顿顶点数哈密顿回路 \(C_{\mathrm{hamilton}}\) 经过的顶点数等于原图顶点数 \(|V|\),每个点恰好一次。
- 本质差异一个约束边,一个约束点,这就是两个问题的分水岭。
实际应用:从DNA到邮局
小王的邮递员日常,竟然在计算机科学里遍地开花。遇到“如何不重复走完所有街道”的问题,本质就是在找欧拉路径。儿童玩的“一笔画”游戏,正是欧拉路径在大众生活中的投影。而更严肃的应用,是 DNA 片段拼接。
在基因组组装中,测序仪会得到大量短的 DNA 片段。研究人员把 k-mer 当作边、k-1-mer 当作顶点,构建 de Bruijn 图,然后寻找一条覆盖所有边的欧拉路径,就能拼出完整序列。另一个经典应用是中国邮递员问题:街道可能必须重复走,需要先给奇度顶点配对补边,让全图变“欧拉化”,再求最短重复路线。
小王的邮递路线,一路延伸到基因测序实验室和物流调度中心。欧拉路径不仅是一笔画游戏,更是一张连接数学与现实世界的隐形地图。
- 补边集合\(M\) 是需要额外重复走的边集合,通过这些边把奇度顶点配对消除。
- 目标函数\(\min_{M} \sum_{e \in M} w(e)\) 表示让重复走的街道总权重最小。
- 欧拉化约束\(\deg_{G+M}(v) \equiv 0 \pmod 2\) 要求加入 \(M\) 后的图每个顶点度数都是偶数,保证存在欧拉回路。
AI 应用场景
基因组组装:DNA片段拼接
阶段:数据预处理与序列组装
解决的问题:高通量测序会产出海量短 DNA 片段,传统拼接算法难以处理重复区域和错误片段。用 de Bruijn 图把 k-mer 作为边、k-1-mer 作为顶点,通过寻找欧拉路径,可以高效地把碎片拼成完整基因组序列,就像小王把一个个小环串成完整邮路。
具体案例:在一项细菌基因组组装任务中,测序仪产生约 50 万条 read,算法构建 de Bruijn 图后,利用 Hierholzer 算法找到覆盖全部 k-mer 的欧拉路径,最终输出完整环状基因组,比传统重叠拼接快了一个数量级。
中国邮递员问题:智慧物流巡检
阶段:路径规划与优化
解决的问题:市政部门清扫街道、快递员派件时,街道往往不允许只走一次,需要设计一条从站点出发、覆盖所有目标街道并返回的最短路线。中国邮递员问题先把奇度顶点配对,添加最小权重复边,再用欧拉回路求解,避免人工拍脑袋的重复绕路。
具体案例:某市环卫公司要对 120 条街道进行洒水作业,系统识别出 18 个奇度路口,通过最小权完美配对补边后运行 Hierholzer 算法,单次巡检总里程缩短约 18%。
机器人路径规划:一笔画式巡检
阶段:运动规划与任务调度
解决的问题:自动导引车在仓库、PCB 板蚀刻或桥梁检测中,需要走遍指定线路且不重复,减少机械磨损和时间损耗。欧拉路径为机器人提供理论可达的一笔画路线,再叠加避障和动力学约束即可落地。
具体案例:蚀刻机台需要在电路板上走完 200 余条铜线,系统将铜线建模为图的边,识别奇度顶点后自动补绕短接路线,最终输出一条无重复主路径,加工时间下降约 25%。
Python 演示
from collections import defaultdict def hierholzer(edges): """返回无向图的欧拉路径/回路顶点序列;不存在时返回 None。""" adj = defaultdict(list) for u, v in edges: adj[u].append(v) adj[v].append(u) odd = [v for v, d in adj.items() if len(d) % 2] if len(odd) not in (0, 2): return None start = odd[0] if odd else edges[0][0] stack = [start] path = [] while stack: v = stack[-1] if adj[v]: u = adj[v].pop() adj[u].remove(v) stack.append(u) else: path.append(stack.pop()) path.reverse() if len(path) - 1 != len(edges): return None return path if __name__ == '__main__': # 两个三角形共用一个顶点,所有顶点度数都是偶数 edges = [(0, 1), (1, 2), (2, 0), (0, 3), (3, 4), (4, 0)] route = hierholzer(edges) print("欧拉回路:", route)
adj = defaultdict(list)给每个路口建一个“可走的街道列表”。odd = [v for v, d in adj.items() if len(d) % 2]找出所有奇度路口,判断一笔画是否可能。if len(odd) not in (0, 2): return None奇度顶点超过 2 个,直接判定无解。start = odd[0] if odd else edges[0][0]若有两个奇度顶点,任选一个当起点;否则从任意路口出发。u = adj[v].pop()从当前路口选择一条未走街道并删除,模拟“走过即划掉”。adj[u].remove(v)无向图还要删掉反向的那条边,确保同一条街不会被走两次。else: path.append(stack.pop())走不动时把路口记入路径,相当于拆下一个小环的端点。if len(path) - 1 != len(edges): return None如果路径边数对不上,说明图不连通,返回无解。
常见误区
误区初学一笔画时,总以为欧拉回路像走迷宫一样,每个路口只能踩一次,否则就不叫“一笔画”。
正解欧拉回路约束的是边不重复,顶点完全可以多次经过。例如两个三角形共享一个顶点,中心路口会经过两次。小王送信关心的是“每条街只走一次”,不是“每个路口只进一次”;真正要求每个顶点只经过一次的是哈密顿回路。
为何常见:“一笔画”的直观画面是笔尖不能回头,很容易被理解成所有点只能碰一次,混淆了边约束和点约束。
误区看到奇数度顶点为 0 或 2,就立刻下结论说可以一笔画,完全忘了检查图是否连通。
正解孤立点可以忽略,但所有含边的顶点必须属于同一个连通分量。如果两片街区之间没有桥,就算每边度数都满足条件,小王也无法从这边走到那边。正确流程是:先检查非零度顶点连通,再检查奇度顶点数量。
为何常见:很多教材把结论简写成“0 或 2 个奇度顶点”,省略了连通前提,初学者跟着记就容易丢条件。
误区以为 Fleury 和 Dijkstra 类似,每次都选当前最短的街道,这样最终路线才最优。
正解Fleury 的贪心不看边长、不看权重,只看这条边是不是桥。只要不是桥,哪怕绕远路也放心走;只有在无路可走时才过桥。它保证的是“能走完”,不是“最短”。
为何常见:听到“贪心”容易联想到最短路,而 Fleruy 的贪心对象是连通性,不是长度。
误区有些人把所有图搜索都想象成 DFS 暴力枚举,认为找欧拉路径和找哈密顿回路一样难。
正解欧拉路径的判定和构造都是多项式时间,Hierholzer 通过删边+栈拼接,每条边只处理一次,复杂度为 \(O(E)\)。Hierholzer 不需要反复试错,因为只要度数条件满足,沿任意未用边走都能走通。
为何常见:把“图的路径问题”一篮子归为 NP 困难,忽略了欧拉路径是少数能被高效求解的特例。
误区以为拿到任意一张街区图,直接找一条欧拉回路就是邮递员的最优路线。
正解现实地图很少天然全偶度,中国邮递员问题先要把奇度顶点两两配对,选择重复边总权重最小的方案使图欧拉化,然后才跑欧拉回路。如果直接跑,许多街道根本走不完全。
为何常见:混淆了“判断欧拉回路是否存在”和“如何通过补边构造欧拉回路”,后者才是真正的优化问题。
面试题
Q1. 判断一个连通无向图存在欧拉回路,需要满足的度条件是?
- A.所有顶点度数都为偶数← 正确答案
- B.恰有两个顶点度数为奇数
- C.所有顶点度数都大于 0
- D.所有顶点度数互不相同
答案:A。欧拉回路要求每条边恰好走一次,回路中每进入一个路口就必须离开一次,因此每个顶点的度数必须成双成对。恰有两个奇度顶点对应欧拉路径而非回路。就像小王要回到邮局,每个路口的进出次数必须配对。
Q2. 小王的一笔画地图中,恰有两个路口连接了奇数条街道,其余路口都连了偶数条街道。他能做到什么?
- A.走出一条欧拉回路并回到邮局
- B.走出一条欧拉路径,从一个奇度路口出发,到另一个奇度路口结束← 正确答案
- C.无法设计任何不重复走街的路线
- D.必须增加一条街道才能一笔画
答案:B。恰好两个奇度顶点时,存在一条欧拉路径,且两个奇度顶点分别担任起点和终点。就像一进一出的两个“死胡同口”;若想闭合回路则不行,因为回路要求进入次数和离开次数在每个路口都成对。
Q3. Hierholzer 算法的核心过程,可以用哪一个生活比喻?
- A.每步都检查桥边,像是过独木桥
- B.把路线拆成多个小环,走完一个环再插到已有环上← 正确答案
- C.用动态规划比较所有子路线长度
- D.每次选最短边扩展
答案:B。Hierholzer 先从起点任意走,删掉走过的边,无路可走时回退,直到找到还有边可走的顶点,再找子环拼接到已有环上。Fleury 才强调桥边避让,因此 B 正确。
Q4. 关于 Fleury 算法,下列说法正确的是?
- A.每一步都优先选择桥边,防止回路断开
- B.每一步都尽可能选择非桥边,以保持剩余图连通← 正确答案
- C.算法复杂度是 O(E),比 Hierholzer 更快
- D.只能处理有向图
答案:B。Fleury 是贪心算法:优先走非桥边,实在没得选才过桥,因为桥一旦被删,剩余边就无法连通。Fleury 需要反复做桥检测,复杂度为 O(E²),比 Hierholzer 慢;它通常用于无向图,故选 B。
Q5. 欧拉回路和哈密顿回路的核心区别是?
- A.前者走遍每条边恰好一次,后者走遍每个顶点恰好一次← 正确答案
- B.前者要求权重最小,后者要求边数最多
- C.前者只适用于树,后者只适用于环
- D.两者完全相同
答案:A。欧拉回路关注边,哈密顿回路关注顶点。邮递员关心每条街,推销员关心每户人家,这正是两个问题的本质差异。A 精准描述了这个区别。
Q6. 在 DNA 片段拼接中,de Bruijn 图上的边表示 k-mer,拼接完整序列等价于寻找?
- A.哈密顿回路
- B.最小生成树
- C.欧拉路径← 正确答案
- D.最大流
答案:C。de Bruijn 图用 k-1-mer 作顶点、k-mer 作边,一条欧拉路径恰好经过每条边一次,等价于把每个 k-mer 用一次并连成完整序列。这是基因组组装的重要建模思路。
Q7. 中国邮递员问题中,原图有 4 个奇度顶点,正确的处理方式是?
- A.直接按偶度顶点图求欧拉回路
- B.删除任意两条边使奇度点变偶
- C.把 4 个奇度顶点两两配对,求最小权重复边方案,加边后求欧拉回路← 正确答案
- D.任选一个顶点作为起点跑 Dijkstra
答案:C。中国邮递员问题要给边添加重复路径,让所有奇度点变成偶度,同时总重复权重最小。4 个奇度点有多种配对方式,需要比较哪种加边方案总权最小,然后再求欧拉回路。直接跑欧拉回路或删边都不符合目标。
Q8. 关于欧拉路径存在性的完整条件,正确的有?
- A.所有非零度顶点必须连通← 正确答案
- B.奇数度顶点个数只能是 0 或 2← 正确答案
- C.图可以为任意多个不连通分量
- D.零度顶点单独存在不影响路径← 正确答案
答案:ABD。孤立顶点可以忽略,因此 D 正确;但多个含边分量之间无法一次性跨越,所以 C 错。A 和 B 是标准判定条件。就像小王没法跨河,但可以无视没有街的空院子。
Q9. Hierholzer 算法相比 Fleury 算法的优点,正确的有?
- A.不需要每次判断桥边← 正确答案
- B.时间复杂度更低,通常为 O(E)← 正确答案
- C.只适用于有向图,不适用于无向图
- D.借助栈和子回路拼接实现← 正确答案
答案:ABD。Hierholzer 用栈记录路径,把子回路在公共顶点拼接,不需要判断桥边,整体复杂度 O(E)。Fleury 每步都要做桥检测,复杂度 O(E²)。C 说反了,两者都能处理无向图,故排除。
Q10. 以下哪些问题可以用欧拉路径/回路建模?
- A.儿童一笔画游戏← 正确答案
- B.邮递员不重复走遍所有街道← 正确答案
- C.旅行商要求访问每个城市一次
- D.DNA 片段拼接← 正确答案
答案:ABD。一笔画和邮递员问题是经典欧拉路径应用;DNA de Bruijn 图组装也找欧拉路径。旅行商要求访问每个顶点一次并回到起点,是哈密顿回路问题,故选 0、1、3。