2023-04-05 欧拉回路和欧拉路径
欧拉回路和欧拉路径
1 欧拉回路#
欧拉回路的起源#
欧拉回路与哈密尔顿回路的区别#
经过所有顶点的回路不一定经过所有边。即哈密尔顿回路不一定是欧拉回路
- 哈密尔顿回路:从一个点出发,沿着边行走,经过
每个顶点
恰好一次,之后再回到出发点 - 欧拉回路: 从一个点出发,沿着边行走,经过
每条边
恰好一次,之后再回到出发点
有哈密尔顿回路不一定有欧拉回路#
有欧拉回路不一定有哈密尔顿回路#
2 欧拉回路的存在性及证明#
欧拉回路存在的性质#
欧拉回路存在的充要条件#
注意以下几个条件
证明:图中每个点的度(临边数)是偶数--->图存在欧拉回路#
最终定论#
对于无向联通图,如果每个点的度是偶数,则图中存在欧拉回路,反过来同样成立
3 实现欧拉回路存在性判断
的代码#
见 实现代码
/** * 判断图中是否有欧拉回路 */ public boolean hasEulerLoop() { // 1.检测联通性 GraphDFS4ConnectedComponents cc = new GraphDFS4ConnectedComponents(graph); if (cc.getConnectedComponentCount()>1){ // 多于一个联通分量肯定就不包含欧拉回路了 return false; } // 2.所有的点的度必须都是偶数 for (int v = 0; v < graph.V(); v++) { if (graph.degree(v)%2==1){ // 如果有一个顶点的度是奇数,直接返回false return false; } } return true; }
4 求解欧拉回路的3个算法#
- 回溯法:指数级时间复杂度,不可用
- Fleury算法:基于贪心算法。时间复杂度是多项式级
O((V+E)^2)
近似为O(E^2)
- 有多条边的时候,不走桥
- 对每一个邻边,判断一下是否是
桥
(桥的查找可以参考第8章) - 不能预处理哪些是桥,只能每到一个点再进行判断邻边是否石桥,因为Fleury算法会动态删除边
- Hierholzer算法:时间复杂度是
O(V+E)
近似等于O(E)
随便从图中找一个环,并把环中的边删除,如果剩下的边可以组成环并且和删掉的环有交点,则把这两个环加起来就是欧拉路径。遇到没有临边的点就把这个点加入到最终的欧拉回路中,因为每个边走一次回退一次所以时间复杂度是O(E)
5 Hierholzer算法模拟#
基于两个栈来完成#
- curPath记录向前访问的路径
- loop记录点没有临边时(过程中被删除了)从curPath中弹出的顺序,即为欧拉路径
注意#
- 1.邻接点访问是从小到大地,因为邻接点矩阵是用TreeSet存地,每访问一个点就压入curPath中
- 2.当一个顶点没有邻接点时就进行回退,并把回退的点压入到loop中,直到回退到有邻接点的顶点,回退是纯栈的动作,不需要有邻边
- 3.欧拉回路的寻找过程中顶点是可以重复访问地,不像哈密尔顿回路
- 4.边用实线表示,虚线表示边已经删除
- 5.下面遍历过程中的"回退"即不断从栈中弹出栈顶元素
详细遍历过程如下#
- 1.访问A,即把A压入curPath栈,此时curPath=[A],A的邻接点有B和C,B序号小,接下来访问B
- 2.访问B,即把B压入curPath栈,此时curPath=[A, B],删除边AB,B的邻接点此时只剩CDE了(因为边A-B被删除了),C序号较小,接下来访问C
- 3.访问C,即把C压入curPath栈,此时curPath=[A, B, C],删除边BC,C的邻接点此时只剩A(因为边B-C被删除了),接下来访问A
- 4.访问A,即把A压入curPath栈,此时curPath=[A, B, C, A], 删除边CA,A此时已经没有邻接点,所以需要回退A
- 5.回退A,即把A从curPath栈中出栈,并压入loop栈,此时curPath=[A, B, C],loop=[A],此时回退到C,C也已经没有邻接点,需要回退C
- 6.回退C,即把C从curPath栈中出栈,并压入loop栈,此时curPath=[A, B],loop=[A, C],此时回退到B,B还有邻接点D、E,所以下面访问D
- 7.访问D,即把D压入curPath栈,此时curPath=[A, B, D],删除边BD,D的邻接点此时只有E(因为边B-D已经被删除),所以接下来访问E
- 8.访问E,即把E压入curPath栈,此时curPath=[A, B, D, E],删除边DE,E的邻接点此时只有B(因为边D-E已经被删除),所以接下来访问B
- 9.访问B,即把B压入curPath栈,此时curPath=[A, B, D, E, B], 删除边E-B,B此时已经没有邻接点,所以需要回退B
- 10.回退B,即把B从curPath栈中出栈,并压入loop栈,此时curPath=[A, B, D, E],loop=[A, C, B],此时回退到E,所以下面访问E
- 11.回退E,即把E从curPath栈中出栈,并压入loop栈,此时curPath=[A, B, D],loop=[A, C, B, E],此时回退到D,所以下面访问D
- 12.回退D,即把D从curPath栈中出栈,并压入loop栈,此时curPath=[A, B],loop=[A, C, B, E, D],此时回退到B,所以下面访问B
- 13.回退B,即把B从curPath栈中出栈,并压入loop栈,此时curPath=[A],loop=[A, C, B, E, D, B],此时回退到A,所以下面访问A
- 14.回退A,即把A从curPath栈中出栈,并压入loop栈,此时curPath=[],loop=[A, C, B, E, D, B, A],此时curPath中为空,loop中存储地即为欧拉回路
- 15.把栈loop元素依次出栈即得到欧拉回路的详细路径。实际欧拉回路正着看反着看都是可以的~~
6 实现Hierholzer算法#
基于递归DFS实现地更优雅易懂~~非递归实现地有很多需要注意的地方,和上一节的理论有些出入,有待更优雅的实现
- 基于递归DFS实现
- 基于非递归DFS实现
Todo:实现地很啰嗦,很不优雅,自己研究下递归和非递归DFS实现的区别,把这个算法改地优雅些
- 测试代码
7 欧拉路径#
欧拉回路和欧拉路径的联系和区别#
- 欧拉回路:从一个点出发,沿着边行走,每条边恰好经过一次,之后回到起始点,结束点和起始点必须是相同
- 欧拉路径:从一个点出发,沿着边行走,每条边恰好经过一次,之后到达结束点,结束点和起始点可以不相同
欧拉路径的性质#
和欧拉回路的性质进行比较
- 欧拉回路存在的性质:对于无向联通图,
每个点的度是偶数==>则图中存在欧拉回路
,反之亦然 - 欧拉路径存在的性质:对于无向联通图,
**除了两个点**,每个点的度是偶数==>则图中存在欧拉回路
,反之亦然。两个点就是指起始点和终止点
Todo:代码实现#
- 判断欧拉路径存在性:简单改造下
hasEulerLoop()
方法即可 - 求欧拉路径:仍然是使用Hierholzer算法
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App