☰
欧拉图与哈密顿图:从判定到算法,彻底分清边覆盖与顶点覆盖
2026/10/15 5:14:57 网站建设 项目流程

1. 科普:欧拉图与哈密顿图——两个一字之差却完全不同的问题

图论里有两块内容特别容易让人搞混,一个是欧拉图(Euler Graph),一个是哈密顿图(Hamilton Graph)。尤其是初学者,经常把这俩画等号,考试的时候换个条件就蒙了。实际上这俩虽然名字挨得近,思想却是两个完全不同的方向:欧拉图关心的是“能不能每条边恰好走一次还不重样”,哈密顿图关心的是“能不能每个顶点恰好访问一次最后回到起点”。一个是边的问题,一个是顶点的问题,这个底层区别决定了它们的判定难度和应用方向。

先说你最容易碰到的场景:编程里路径规划、电路板设计、社交网络分析、物流配送路线优化,这些地方都会用到这两个概念。欧拉图是“边”层面的一笔画问题,哈密顿图是“顶点”层面的旅行商问题。你理解了这俩的差异,就相当于拿到了一个很顺手的图论分析工具。

我最初学这块的时候也是先记住了七桥问题的故事,但后来发现光记故事没用,得真的会用判定条件去做题、写代码、建模。所以这篇就把欧拉图和哈密顿图从定义到判定、从实例到算法拉通讲一遍,你如果正在准备算法竞赛、期末考试或者做项目方案设计,看完应该能直接省掉一大段自己摸索的时间。

另外有个事实得提前摆出来:这两个问题的难度根本不是一个量级。欧拉图的判定和构造有一套干净利落的理论,只要满足条件你就能轻松找出回路;哈密顿图至今没有一个充要条件能像欧拉那样“一锤定音”,判断起来相当费劲。这个区别如果你现在就知道,后面学习的时候心里会踏实很多,不至于在两个问题之间反复横跳。

2. 先从定义聊起:一笔画与周游世界的区别

2.1 欧拉图:每条边恰好走一次

先说欧拉图。它在无向图中是指存在一条回路,能够经过图中每一条边恰好一次,然后回到起点。这条回路就叫欧拉回路。如果去掉“回到起点”这个要求,只要求一条路径经过每条边恰好一次,就叫欧拉通路,或者叫欧拉迹。图中有欧拉回路的图就是欧拉图,只有欧拉通路的图则叫半欧拉图。

这个定义的灵感来源很直观——一笔画问题。你小时候肯定玩过那种“一笔画完整个图形,笔不离纸、线不重复”的谜题。如果一张图能用一笔画完并且终点和起点重合,那它就是欧拉图;如果一笔画完但终点和起点不重合,那它只有欧拉通路。

举个例子。想象一个长方形,四条边连起来形成闭合回路。你从某个顶点出发,沿着边走一圈,每条边都会经过一次,这就是最简单的欧拉回路。如果这个图形是一个“日”字形的三横两竖结构,你从中间横线的左端出发,画完整条线到右端,中间不会断、线也不重复,这实际上就是一条欧拉通路。

我上课的时候爱打一个比方:欧拉图相当于你逛一个小区,要求每条路都必须走到,但任何一条路都不允许走第二遍,最后还得回到自己的车旁边。那条从停车场出发再回到停车场的路线,如果存在,就说明这个小区路网能构成欧拉回路。至于你走不走得到每一个楼栋门口,欧拉图不管这个。

2.2 哈密顿图:每个顶点恰好访问一次

再看哈密顿图。它的定义是:存在一条回路,经过图中每一个顶点恰好一次,然后回到起点。这条回路叫哈密顿回路。如果只要求经过每个顶点恰好一次,不要求回到起点,就叫哈密顿通路。

这个概念来源于一个数学游戏,叫做“周游世界”。想象一个正十二面体的图,每个顶点代表一座城市,每条边代表两座城市之间有路,哈密顿回路的任务就是找出一个环,让你依次访问每个城市恰好一次,最后回到出发的城市。听起来和欧拉图有点像,但请注意:这里完全不关心每条边走几次,只关心顶点是否全部被访问且不重复。

还是类比说明。你同样在小区里逛,但这次规则变了:你只要求拜访每一栋楼的楼门口一次,至于走的是大路还是小路、某条路走没走第二遍,完全不重要,重要是你把所有楼都拜访一遍且每栋只拜访一次。这个问题的难度会瞬间飙升——你规划的路线需要考虑楼与楼之间的连接顺序,而不是简单地把每条路都走一遍。

2.3 一个容易被忽视的细节:边不重复与顶点不重复

学习的时候一定要把这个核心差异记死:欧拉图是对“边集合”的遍历,哈密顿图是对“顶点集合”的遍历。这两个问题的约束条件表面上都是“恰好一次”,但对象完全不同。

更微妙的是,欧拉图允许重复经过某些顶点。比如一条欧拉回路,为了把每条边都走完,你完全可能多次路过同一个路口。但哈密顿回路不允许重复经过顶点,它把每个顶点看成需要打卡的地点,打一次卡就过了,不能再回来。

我还记得自己第一次做图论题时错过的一个坑:一个图是欧拉图,同时也有可能是哈密顿图,这两个身份可以同时成立,也可以只成立一个,还可以都不成立。比如一个简单的五边形,它既存在欧拉回路,也存在哈密顿回路;但一个带多个桥的图,可能满足欧拉判定条件,却根本找不到哈密顿回路。别把它们理解成“互斥”关系。

3. 判定条件是关键:为什么欧拉图能“秒判”,哈密顿图却难搞

3.1 欧拉图的判定定理

欧拉图的判定条件非常干净,而且有一套完整的充要定理。对于无向图,一个连通图是欧拉图,当且仅当图中所有顶点的度数都是偶数。对于有向图,判定条件变成:每个顶点的入度等于出度。此外,如果图中刚好只有两个顶点的度数是奇数,其他顶点度数都是偶数,那么这个图一定存在欧拉通路,但不一定有欧拉回路。

为什么度数条件这么关键?你可以这样理解:一条回路中间,每当你“进入”一个顶点,就必须“离开”这个顶点,所以每个顶点的“进入次数”和“离开次数”必须相等,累计下来就是该顶点的度数是偶数。回路起点比较特殊,它既是起点也是终点,所以它的度也必须贡献出“一次进入、一次离开”,因此也必须是偶数。如果只有两个奇度顶点,那它们只能作为欧拉通路的起点和终点,回路就套不上了。

我第一次学到这个定理的时候觉得有点“反直觉”——你看一个图的边上有没有重复和路径规划,居然只看顶点的度数的奇偶性就能判断“能不能一笔画成”,甚至不需要真的去画路径。这就是理论的力量:它把无穷多可能的路径方案压缩成了对局部结构的检查。

实操判断的时候,你只需要两步:第一步检查图是否连通(不连通直接排除),第二步数每个顶点的度数,看奇偶分布。两个步骤都不费什么劲儿,是用手算也能搞定的级别。

3.2 哈密顿图至今无充要条件

哈密顿图的判定就没有这么幸运了。目前没有找到一个像“所有顶点度数均为偶数”这样简洁的充要条件。理论界给出了很多充分条件和必要条件,但都不能覆盖所有情况。换句话说,给定一个复杂的图,你要么能找到一条明确的哈密顿回路,要么得想各种办法证明不存在这样的回路,而“证明不存在”这件事本身在计算上非常困难。

从计算复杂度来说,判断一个图是否存在哈密顿回路是NP完全问题,这意味着到目前为止,没有人能设计出一个在多项式时间内解决它的通用算法。欧拉图判定是O(V+E)级别的线性复杂度,哈密顿图判定则是指数级别的绝望感。这俩的“算法待遇”差别巨大,也是面试和教学中特别爱考对比的地方。

必要条件里有一个比较好用的:如果图是哈密顿图,那么删除图中任意k个顶点后,剩下的连通分量个数不会超过k。你可以拿这个必要条件去排除一些明显不可能的图。比如你删掉3个顶点后,图碎成了5个互不相连的部分,那它肯定不是哈密顿图。但它只是必要条件,不满足一定不是,满足也未必是。

充分条件里有一个很著名的邦迪定理:如果对于图中任意两个不相邻的顶点u和v,满足deg(u) + deg(v) ≥ n,那么这个图就是哈密顿图。这个条件对“稠密图”很有效,因为边很多的情况下顶点之间连接丰富,基本能判定存在哈密顿回路。对于稀疏图、不规则图,这个条件往往不生效,你就只能上搜索算法了。

3.3 半欧拉图的判断与“一笔画”做题技巧

考试里出现频率极高的题是给你一张图,问你“能不能一笔画成?如果能,怎么画”。这个题的判断就是看奇度顶点个数:0个奇度顶点,一定有欧拉回路,可以从任意顶点出发画成闭环;2个奇度顶点,一定有欧拉通路,必须从其中一个奇度顶点出发,在另一个奇度顶点结束。超过2个奇度顶点,就不可能一笔画成。

举个例子。一个经典的“田”字形图,9个交点若干条线构成田字。你数一下外框四个角顶点的度数,每个角连接2条边,是偶数;中间交叉点连接4条边,也是偶数,所以田字形图是欧拉图,可以一笔画成。可要画一个“品”字形(三个口字堆叠),你会发现有些顶点度数是奇数,至少出现3个以上奇度顶点,那就不可能一笔画成。

这里有个技巧:做题时先找奇度顶点个数。别一上来就试图画路径,奇度顶点超过2个直接判死刑,连画都不用画。如果刚好0个或2个,再动手找路径也不迟。这个顺序能省很多草稿纸。

4. 核心问题建模:从欧拉图到邮递员问题,从哈密顿图到旅行商问题

4.1 欧拉图模型的经典应用——中国邮递员问题

现实中很多问题本质上就是在问:“这个图能不能让我每条路走一遍?”其中最著名的就是中国邮递员问题。邮递员从邮局出发,需要走过自己负责路段的每一条道路,最后回到邮局,目标是让总路程最短。如果道路网络本身是欧拉图,那邮递员只需要找到一条欧拉回路,一条都不多走,完美解决。

但如果路网不是欧拉图,存在奇度顶点,那邮递员就不得不重复走某些道路。这时候问题就变成:怎么通过给某些边“加倍”(走两遍),把原始图变成欧拉图,并且让加倍的边总长最短。这个问题的解法依赖于求解图上奇度顶点之间的最短路径匹配,属于可以在多项式时间内解决的经典模型之一。

我在实际项目里处理过类似的任务,比如园区巡检路线规划。园区里的道路不是所有路口度数都是偶数,巡检员又要求每条路都得覆盖到,这时候就不能直接套欧拉回路,而要先跑一遍最小权匹配,把奇度顶点配对起来,再叠加最短路径,最后用弗勒里算法找回路。你如果只背了欧拉图定义没有这层建模意识,到了实际场景会很懵。

4.2 哈密顿图模型的经典应用——旅行商问题

哈密顿图对应的经典实际问题是旅行商问题(TSP):一个销售要拜访n个城市,每个城市恰好去一次,最后回到出发城市,要求总路线最短。不夸张地说,这就是哈密顿回路加上了权值(边权重)的最短版本。如果所有城市之间的通路权值都已知,你要找的就是一条最短的哈密顿回路。

TSP的求解难度比中国邮递员问题高得多,因为它在n个城市之间需要考虑的回路数量是阶乘级的。四个城市还不是问题,十个城市就已经很烦了,二十个城市靠暴力枚举基本不可行。实际工程中一般用近似算法、启发式算法来求解,比如贪心、模拟退火、遗传算法、蚁群算法,都是为了在可接受时间内找到一条“足够好”的路线,而不是严格最优。

在一个物流配送系统的模拟项目里,我遇到过TSP的实际约束:订单量不大但配送点分散,司机希望尽量不走回头路。我先用贪心最近的邻接顶点构造初始回路,再用2-opt局部搜索优化,最后得到的结果比纯手工排路线缩短了大概15%的总路程。这个过程中我并不需要判断图是不是哈密顿图——如果配送点之间的路网本身不连通,那当然没有哈密顿回路,这时候就得先处理连通性问题。所以我一直强调:哈密顿回路理论是建模的“天花板”,工程上要做的是在现实限制下逼近它。

4.3 用图的实例判断“该用哪种图模型”

拿一张具体的图来练手,比如一个三角形连接三条边。它既是欧拉图(每个顶点度数为2,偶数),又有哈密顿回路(绕着三角形走一圈即可)。再看一个“悬挂树枝”图:一条长链在端点挂了一个三角形的结构。这个图里显然存在奇度顶点,不可能是欧拉图;但它的顶点数量少,你手动排一下,也有可能找出哈密顿回路或通路。

实际工程判断的时候,我建议你按这个思路走:如果任务要求覆盖“所有边/所有路段”,优先想到欧拉图模型;如果任务要求覆盖“所有点/所有客户”,优先想到哈密顿图模型。这两个模型对应的是两种完全不同的资源分配逻辑,选错了模型,后面算法直接崩盘。

5. 两种核心算法实践:找欧拉回路的弗勒里算法与希尔霍泽算法

5.1 弗勒里算法(Fleury Algorithm):贪心思想找欧拉回路

弗勒里算法是找欧拉回路的经典算法,思想上像极了“过桥时候小心别把桥拆了你就回不来”。具体做法:从起点出发,每一步选择一条边走,但有唯一的限制——除非没有别的选择了,否则不要走“桥边”(割边)。桥边一旦走过,图就会被切成两个互不相连的部分,剩下没走的边就永远走不到了。这个限制保证了你不会把自己困在某个子图里。

算法的流程是这样的:初始化当前顶点为起点;在当前顶点的所有邻边中,选择一条不是桥的边(除非只剩桥边);把这条边加入路径,删除这条边,移动到下一个顶点;重复直到所有边都被删除。如果最后你走回了起点且边都用完了,得到的就是一条欧拉回路。

弗勒里算法的时间复杂度不是最优的,因为它在每次选择边的时候都要判断一条边是否是桥,这个判断本身就是一次图的遍历过程,整体复杂度大约O(E^2)级别。对大多数考试题、小规模图,这已经足够了。我第一次实现时还踩过一个坑:判断桥边是在删除之后图是否连通,但要注意原图本来就是连通图,桥判断需要基于当前剩余边构成的图来做,不能用原始图判断。

注意:弗勒里算法用来找欧拉回路时,起点必须是任意顶点(在无向图所有顶点度数均为偶数的情况下)。如果是找欧拉通路,起点必须选一个奇度顶点。

5.2 希尔霍泽算法(Hierholzer Algorithm):更高效的线性时间方案

希尔霍泽算法比弗勒里更高效,复杂度是O(E),在工程上基本是首选。它的思想是分而治之:先随便走一条闭环,然后把闭环合并到能继续走的分支上,最终得到一个大的欧拉回路。

具体步骤如下:从任意起始顶点出发,沿着未访问过的边走,直到回到起始顶点,此时得到一条子回路。接下来检查这条子回路上是否有顶点还连着未访问的边,如果有就以该顶点为起点再走一个闭环,把这个闭环插入到原回路中对应位置。不断重复,直到所有边都被纳入。

希尔霍泽算法实现起来比弗勒里稍微绕一点,但代码量其实不大。因为它在每一步选择边走的时候不用判断桥,所以复杂度低了很多。在写代码的时候,核心是用一个栈来模拟递归过程,并且注意边的删除,避免重复访问。

我实际测试过:在包含500条边的随机连通图上,希尔霍泽算法几乎瞬间完成,而弗勒里算法因为每次都要做桥判断,明显慢很多。所以只要条件允许,工程上我建议直接用希尔霍泽算法。

5.3 Python示例代码:用希尔霍泽算法求欧拉回路

下面是一段简化的Python示例,用邻接表存储图,通过希尔霍泽算法输出欧拉回路:

def hierholzer(adj): # adj: dict,顶点 -> 邻居列表(列表中的元素会动态删除) from collections import defaultdict edge_count = defaultdict(int) for u in adj: for v in adj[u]: edge_count[(u, v)] += 1 start_vertex = next(iter(adj)) stack = [start_vertex] circuit = [] while stack: u = stack[-1] if adj[u]: v = adj[u].pop() # 删除反向边 if v in adj and u in adj[v]: adj[v].remove(u) stack.append(v) else: circuit.append(stack.pop()) return circuit[::-1] # 示例:一个三角形图 graph = { 0: [1, 2], 1: [0, 2], 2: [0, 1] } result = hierholzer(graph) print(result) # [0, 2, 1, 0] 或类似顺序

注意这份代码里删反向边的时候用了 adj[v].remove(u),这要求图是简单无向图,没有平行边。如果有平行边,用邻接表里保存边ID的方式更严谨。我写这个例子是为了让你看核心逻辑,实际项目里建议用边ID或者把边封装成对象。

5.4 哈密顿回路的搜索:回溯法与剪枝优化

哈密顿回路的求解没有“优雅的线性算法”,但小规模图用回溯法是完全可以接受的。核心思路是:从一个顶点出发,每次尝试移到未访问的相邻顶点,如果最后能回到起点且所有顶点都访问过,就找到一个哈密顿回路。如果某条路走不通,就回溯换一条路。

关键优化点在剪枝。每次尝试进入新顶点之前,先检查剩下的未访问顶点是否连通;如果不连通,直接剪掉这条分支。还有一个常用技巧是优先选择度数最小的未访问顶点作为下一步目标,这样可以尽早触发分支失败,减少搜索空间。

我给一个教学Demo写过求解器,顶点数在15以内时回溯法表现还可以,但顶点数到20以上就会明显变慢。如果你想处理更大规模的图,就要考虑启发式或者近似算法了。不过考试和课程设计一般不会让你跑几百个顶点的哈密顿回路,理解和实现回溯法就够用了。

5.5 回溯法求哈密顿回路的Python示例

这是一段简单直观的Python回溯代码,适合小规模图:

def hamilton_cycle(graph, start=0): n = len(graph) path = [start] visited = [False] * n visited[start] = True def dfs(v, count): if count == n: # 检查是否回到起点 return start in graph[v] for nxt in graph[v]: if not visited[nxt]: visited[nxt] = True path.append(nxt) if dfs(nxt, count + 1): return True path.pop() visited[nxt] = False return False if dfs(start, 1): return path return None # 示例:五边形图 graph = { 0: [1, 4], 1: [0, 2], 2: [1, 3], 3: [2, 4], 4: [3, 0] } cycle = hamilton_cycle(graph) print(cycle) # [0, 1, 2, 3, 4]

这个实现最需要注意的地方是最后“能否回到起点”的判断,很多初学者忘了这步,结果输出的是哈密顿通路而不是回路。调试的时候建议先拿三角形、正方形这种简单图验证逻辑,确认无误再跑复杂图。

6. 工程与考试中的高频坑:欧拉图与哈密顿图常见问题速查

6.1 判断顺序搞错:没有先验证连通性

最大的坑就是不看连通性直接数度数。一个图就算所有顶点度数都是偶数,只要它不连通,也不是欧拉图。比如两个完全独立的三角形放在一起,所有顶点度数都是2,但它们之间有边连接吗?没有。那你不可能找到一条覆盖所有边的回路,因为它们根本在两个世界里。

做题习惯建议:第一步永远是判断连通性。无向图可以用DFS/BFS检查是不是单个连通分量;有向图要强连通性检查(对应欧拉回路)或者弱连通性检查(对应欧拉通路)。很多同学在期末考丢分就丢在“忘了检查连通性”上,太可惜了。

6.2 把“哈密顿通路”当成“哈密顿回路”

另一个高频错误是把哈密顿通路误当成哈密顿回路。哈密顿通路只要求经过每个顶点一次,不要求回到起点;哈密顿回路要求回到起点。判断和构造时只差一个“回得来”的条件,但一旦题目问的是回路,你给出的路径必须闭合。

我见过有学生在做图论作业时,画了一条从A到B的路径,经过了所有顶点,觉得万事大吉。结果题目要求的是回路,B必须有一条边连回A才成立。这种错误不是不会做,是审题不细。

6.3 弗勒里算法里“桥边”判断失误

弗勒里算法虽然简单,但桥判断很爱出错。每一步选择边的时候,要用“当前剩余边构成的图”来判断桥,而不是用“原始完整图”。你每走一条边就删一条边,图的连通状态一直在变。如果你用原始图判断桥,很可能把一些根本不是桥的边误当成桥,导致找不到正确的欧拉回路。

实操建议:写弗勒里算法时,先用DFS统计当前图中哪些边是割边,再结合“非桥边优先”的规则选边。图形规模较小的时候可以采用暴力的O(E)桥判定,不必上复杂的Tarjan算法,代码写起来反而更稳。

6.4 “度数和”陷阱:用握手定理检验结果

关于欧拉回路,有一个很好用的自检工具:握手定理。一个无向图的所有顶点度数之和等于边数的两倍。如果你输入的图满足欧拉回路所需的所有顶点度数为偶数,那么所有顶点度数之和肯定是偶数。反过来,如果度数之和是奇数,那一定数错了。

有时候手算图题,边数多了容易数漏。我每次都会用度数之和=2E来验证自己的度数统计是否准确。一旦发现不满足,先别急着判断欧拉图,检查是不是漏数了某条边。

6.5 哈密顿充分条件不满足不代表不存在回路

这可能是新手做哈密顿题时最容易误解的一句话。邦迪定理说“满足条件一定存在哈密顿回路”,但它没说“不满足条件一定不存在”。很多同学用充分条件去判“死”一个图,得出“这个图没有哈密顿回路”的结论,但实际上这个图可能是哈密顿图,只是不满足这个充分条件而已。

拿考试来说,要证明一个图“不是”哈密顿图,唯一靠谱的思路是用必要条件(比如删顶点连通分量条件)去推翻。要证明一个图“是”哈密顿图,要么直接构造出一条显式的回路,要么用充分条件。别把逻辑方向搞反了。

6.6 有关判定条件与算法选择速查

为了帮你快速定位问题,我做了一个小表格。你在做题或者项目取舍的时候可以对照着看:

问题类型判定/求解依据时间复杂度常用场景
判断欧拉回路(无向)连通 + 所有顶点度数为偶数O(V+E) 遍历一笔画问题、道路巡检
判断欧拉通路(无向)连通 + 恰好2个奇度顶点O(V+E) 遍历一笔画问题、迷宫路径
找出欧拉回路希尔霍泽算法O(E)邮递员路线、电路板检测
找出欧拉通路弗勒里/希尔霍泽变形O(E)或O(E²)物流路径、打印路径规划
判断哈密顿回路无充要条件,只能用必要条件/充分条件/搜索NP完全理论题、结构验证
找出哈密顿回路(小规模)回溯法 + 剪枝指数级但可优化教学演示、竞赛小规模
最短哈密顿回路(TSP)启发式/近似算法多项式可解但非最优物流配送、路径优化

从这个表可以看出来,欧拉图在理论和工程上都非常“温柔”,哈密顿图则是一个需要小心伺候的“硬骨头”。理解了这一点,你在方案设计阶段就能做出更合理的预期,而不是在需求评审会上拍脑袋说“我可以用算法求出最优配送路线”——如果数据量大,这句话大概率会翻车。

7. 个人经验:怎么学才能把这两个概念真正内化

以我带过的学生和我自己学习的经验来说,最有效的学习路径不是死记定义和定理,而是拿真实图“上手盘”。比如你先随便画几个点,再随机连边,然后用“度数奇偶性”判断它是不是欧拉图,再用回溯法写代码找哈密顿回路。这个过程看起来简单,但能把定理和算法串起来。

我强烈建议做三件事。第一,亲手画一张非欧拉图,然后用“加边”的方法把它变成欧拉图,感受一下路网改造的思路。第二,找一个10个顶点以内的图,手写一遍弗勒里算法,体会“不走桥”的微妙感觉。第三,把同一个图分别用欧拉和哈密顿的标准去分析,明确区分“边的覆盖”和“顶点的覆盖”的差异。

做这些练习的时候,我还会提醒自己:别被名字唬住。欧拉图和哈密顿图“一字之差”带来的计算难度天壤之别,恰恰是图论最迷人的地方——有些问题表面上差不多,本质却差了十万八千里。你只有亲手做过一遍,才算真正理解这个“差”在哪里。

最后分享一个实用小技巧:如果面试或者答辩时你被问到这两种图的差异,直接一句话回答——“欧拉图管边,哈密顿图管点;欧拉图有简单判定,哈密顿图没有;欧拉图对应路径覆盖,哈密顿图对应顶点覆盖”。这三句话出来,对方基本就知道你是真懂还是背过概念。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询