贪心算法应用:欧拉路径(Fleury算法)详解
Java中的贪心算法应用:欧拉路径(Fleury算法)详解
一、欧拉路径与欧拉回路基础
1.1 基本概念
欧拉路径(Eulerian Path)是指在一个图中,经过图中每一条边且每一条边只经过一次的路径。如果这条路径的起点和终点相同,则称为欧拉回路(Eulerian Circuit)。
1.2 存在条件
-
无向图欧拉回路存在条件:
- 图是连通的
- 所有顶点的度数都是偶数
-
无向图欧拉路径存在条件:
- 图是连通的
- 恰好有两个顶点的度数是奇数(这两个顶点分别是路径的起点和终点)
-
有向图欧拉回路存在条件:
- 图是强连通的
- 每个顶点的入度等于出度
-
有向图欧拉路径存在条件:
- 图是弱连通的
- 恰好有一个顶点的出度比入度大1(起点)
- 恰好有一个顶点的入度比出度大1(终点)
- 其他所有顶点的入度等于出度
二、Fleury算法原理
Fleury算法是一种用于在图中寻找欧拉路径或欧拉回路的贪心算法。它的核心思想是:尽可能不选择桥边(除非没有其他选择)。
2.1 算法步骤
- 检查图是否满足欧拉路径/回路的存在条件
- 选择起始顶点:
- 对于欧拉回路:可以选择任意顶点
- 对于欧拉路径:必须选择奇数度数的顶点之一
- 递归/迭代选择边:
- 选择一条非桥边(除非没有其他选择)
- 删除已选择的边
- 移动到下一个顶点
- 重复上述过程直到所有边都被遍历
2.2 为什么是贪心算法
Fleury算法在每个步骤中都做出局部最优选择(选择非桥边),以期最终得到全局最优解(完整的欧拉路径)。这种"每一步都做出当前看来最佳选择"的策略正是贪心算法的核心特征。
三、Java实现Fleury算法
3.1 图的表示
我们使用邻接表来表示图:
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;public class FleuryAlgorithm {private int vertices; // 顶点数private LinkedList<Integer>[] adjacencyList; // 邻接表// 构造函数public FleuryAlgorithm(int vertices) {this.vertices = vertices;adjacencyList = new LinkedList[vertices];for (int i = 0; i < vertices; i++) {adjacencyList[i] = new LinkedList<>();}}// 添加边public void addEdge(int u, int v) {adjacencyList[u].add(v);adjacencyList[v].add(u); // 无向图}// 删除边public void removeEdge(int u, int v) {adjacencyList[u].remove((Integer) v);adjacencyList[v].remove((Integer) u);}
}
3.2 检查图是否连通(DFS方法)
// 深度优先搜索检查连通性
private void dfs(int v, boolean[] visited) {visited[v] = true;for (int neighbor : adjacencyList[v]) {if (!visited[neighbor]) {dfs(neighbor, visited);}}
}// 检查图是否连通
public boolean isConnected() {boolean[] visited = new boolean[vertices];// 找到第一个度数不为0的顶点int startVertex = 0;for (int i = 0; i < vertices; i++) {if (adjacencyList[i].size() != 0) {startVertex = i;break;}}// 如果没有边,认为是连通的if (startVertex == -1) {return true;}// 从该顶点开始DFSdfs(startVertex, visited);// 检查所有度数不为0的顶点是否都被访问过for (int i = 0; i < vertices; i++) {if (adjacencyList[i].size() > 0 && !visited[i]) {return false;}}return true;
}
3.3 检查边是否为桥(DFS计数方法)
// 计算从u出发可到达的顶点数
private int dfsCount(int u, boolean[] visited) {visited[u] = true;int count = 1;for (int v : adjacencyList[u]) {if (!visited[v]) {count += dfsCount(v, visited);}}return count;
}// 检查u-v是否是桥
private boolean isBridge(int u, int v) {// 计算u的邻接顶点数int degree = adjacencyList[u].size();if (degree == 1) {return false; // 只有一条边,不是桥}// 计算删除u-v前从u可到达的顶点数boolean[] visited = new boolean[vertices];int count1 = dfsCount(u, visited);// 临时删除边u-vremoveEdge(u, v);// 计算删除u-v后从u可到达的顶点数visited = new boolean[vertices];int count2 = dfsCount(u, visited);// 恢复边u-vaddEdge(u, v);// 如果count1 > count2,说明u-v是桥return count1 > count2;
}
3.4 Fleury算法核心实现
// 打印欧拉路径/回路
public void printEulerPath() {// 检查图是否连通if (!isConnected()) {System.out.println("图不连通,不存在欧拉路径");return;}// 计算奇数度数的顶点数int oddDegreeCount = 0;int startVertex = 0;for (int i = 0; i < vertices; i++) {if (adjacencyList[i].size() % 2 != 0) {oddDegreeCount++;startVertex = i;}}// 检查是否存在欧拉路径或回路if (oddDegreeCount > 2) {System.out.println("图中没有欧拉路径或回路");return;}// 开始寻找路径System.out.println("欧拉路径/回路为:");printEulerUtil(startVertex);System.out.println();
}// 递归打印欧拉路径
private void printEulerUtil(int u) {// 遍历所有邻接顶点for (int v : adjacencyList[u]) {// 如果边u-v不是桥,或者只剩下这条边,则选择它if (!isBridge(u, v) || adjacencyList[u].size() == 1) {System.out.print(u + "-" + v + " ");// 删除这条边removeEdge(u, v);// 继续从v开始printEulerUtil(v);break;}}
}
3.5 完整测试代码
public static void main(String[] args) {// 示例1:欧拉回路FleuryAlgorithm g1 = new FleuryAlgorithm(4);g1.addEdge(0, 1);g1.addEdge(0, 2);g1.addEdge(1, 2);g1.addEdge(2, 3);g1.addEdge(3, 0);g1.printEulerPath();// 示例2:欧拉路径FleuryAlgorithm g2 = new FleuryAlgorithm(4);g2.addEdge(0, 1);g2.addEdge(0, 2);g2.addEdge(1, 2);g2.addEdge(2, 3);g2.printEulerPath();// 示例3:没有欧拉路径FleuryAlgorithm g3 = new FleuryAlgorithm(3);g3.addEdge(0, 1);g3.addEdge(1, 2);g3.addEdge(2, 0);g3.addEdge(0, 1); // 多重边g3.printEulerPath();
}
四、算法复杂度分析
-
时间复杂度:
- 检查连通性(DFS):O(V + E)
- 检查桥边:每次检查需要O(E)时间
- 对于每条边,我们可能需要检查它是否是桥,因此总时间复杂度为O(E²)
-
空间复杂度:
- 主要取决于图的表示和递归深度
- 邻接表:O(V + E)
- 递归栈:最坏情况下O(E)
五、优化与改进
5.1 桥边检测优化
原始的Fleury算法在每一步都需要检测桥边,这导致了较高的时间复杂度。可以使用以下方法优化:
- 动态维护桥边信息:使用更高级的数据结构(如动态图算法)来维护桥边信息
- Hierholzer算法:另一种更高效的欧拉路径算法,时间复杂度为O(E)
5.2 Hierholzer算法简介
虽然Fleury算法是经典的贪心算法,但Hierholzer算法通常更高效:
public void hierholzerAlgorithm() {// 1. 检查欧拉路径存在条件(同Fleury算法)// 2. 初始化栈和路径Stack<Integer> stack = new Stack<>();List<Integer> path = new ArrayList<>();// 3. 选择起始顶点(同Fleury算法)int startVertex = ...;stack.push(startVertex);// 4. 主循环while (!stack.isEmpty()) {int current = stack.peek();if (adjacencyList[current].size() == 0) {path.add(stack.pop());} else {int next = adjacencyList[current].get(0);stack.push(next);removeEdge(current, next);}}// 5. 输出路径Collections.reverse(path);System.out.println(path);
}
六、实际应用场景
欧拉路径和Fleury算法在实际中有多种应用:
- 邮路问题:邮递员如何走过所有街道且不重复
- DNA序列组装:将短DNA片段组装成完整序列
- 网络路由:设计网络数据包的路由路径
- 电路板钻孔:在电路板上钻孔的最优路径规划
- 笔画问题:判断图形是否可以一笔画成
七、常见问题与解决方案
7.1 如何处理多重图?
多重图(有重复边的图)需要特殊处理:
- 邻接表中存储边时,可以使用带计数的结构
- 删除边时减少计数而不是完全移除
7.2 如何处理极大图?
对于极大图,递归实现可能导致栈溢出:
- 改用迭代实现
- 增加栈大小(-Xss JVM参数)
- 使用更高效的算法如Hierholzer
7.3 如何验证找到的路径确实是欧拉路径?
验证方法:
- 检查路径长度是否等于边数
- 检查每条边是否只出现一次
- 检查路径是否连续(相邻顶点间有边)
八、扩展与变种
- 中国邮路问题:允许重复经过某些边,求最短路径
- 有向图欧拉路径:调整算法处理有向边
- 加权图欧拉路径:在有权图中寻找最优欧拉路径
- 混合图欧拉路径:同时包含有向边和无向边的图
九、总结
Fleury算法是贪心算法在图论中的一个经典应用,它通过在每个步骤中尽可能选择非桥边来构建欧拉路径。虽然它的时间复杂度不是最优的,但其思路清晰,易于理解,是学习贪心算法和欧拉路径的绝佳范例。
Java实现时需要注意图的表示、连通性检查、桥边检测等关键点。对于实际应用,可以考虑使用更高效的算法如Hierholzer,但理解Fleury算法对于掌握贪心算法的应用场景和局限性非常有帮助。
欧拉路径问题及其解决方案在计算机科学和实际应用中都有着广泛的应用,掌握这些算法对于解决现实世界中的路径优化问题具有重要意义。
更多资源:
https://www.kdocs.cn/l/cvk0eoGYucWA
本文发表于【纪元A梦】!
相关文章:

贪心算法应用:欧拉路径(Fleury算法)详解
Java中的贪心算法应用:欧拉路径(Fleury算法)详解 一、欧拉路径与欧拉回路基础 1.1 基本概念 欧拉路径(Eulerian Path)是指在一个图中,经过图中每一条边且每一条边只经过一次的路径。如果这条路径的起点和…...

【算法设计与分析】实验——二维0-1背包问题(算法分析题:算法思路),独立任务最优调度问题(算法实现题:实验过程,描述,小结)
说明:博主是大学生,有一门课是算法设计与分析,这是博主记录课程实验报告的内容,题目是老师给的,其他内容和代码均为原创,可以参考学习,转载和搬运需评论吱声并注明出处哦。 要求:3-…...
P12592题解
题目传送门 思路 由于题目中说了可以任意交换两个字符的位置,我们只需要判断这个字符串是否满足回文串的条件即可。 代码: #include<bits/stdc.h> using namespace std; int a[30]; int main(){int T;cin>>T;while(T--){fill(a,a29,0);/…...
ffmpeg命令(二):分解与复用命令
分解(Demuxing) 提取视频流(不含音频) ffmpeg -i input.mp4 -an -vcodec copy video.h264-an:去掉音频 -vcodec copy:拷贝视频码流,不重新编码 提取音频流(不含视频)…...

【Git】View Submitted Updates——diff、show、log
在 Git 中查看更新的内容(即工作区、暂存区或提交之间的差异)是日常开发中的常见操作。以下是常用的命令和场景说明: 文章目录 1、查看工作区与暂存区的差异2、查看提交历史中的差异3、查看工作区与最新提交的差异4、查看两个提交之间的差异5…...

deepseek原理和项目实战笔记2 -- deepseek核心架构
混合专家(MoE) 混合专家(Mixture of Experts, MoE) 是一种机器学习模型架构,其核心思想是通过组合多个“专家”子模型(通常为小型神经网络)来处理不同输入,从而提高模型的容…...

在 MATLAB 2015a 中如何调用 Python
在 MATLAB 2015a 中调用 Python 可通过系统命令调用、.NET 交互层包装、MEX 接口间接桥接、环境变量配置四种方式,但因该版本对 Python 支持有限,主要依赖的是系统命令调用与间接脚本交互。其中,通过 system() 函数调用 Python 脚本是最简单且…...

房屋租赁系统 Java+Vue.js+SpringBoot,包括房屋类型、房屋信息、预约看房、合同信息、房屋报修、房屋评价、房主管理模块
房屋租赁系统 JavaVue.jsSpringBoot,包括房屋类型、房屋信息、预约看房、合同信息、房屋报修、房屋评价、房主管理模块 百度云盘链接:https://pan.baidu.com/s/1KmwOFzN9qogyaLQei3b6qw 密码:l2yn 摘 要 社会的发展和科学技术的进步…...

华为OD机试真题——生成哈夫曼树(2025B卷:100分)Java/python/JavaScript/C/C++/GO六种最佳实现
2025 B卷 100分 题型 本文涵盖详细的问题分析、解题思路、代码实现、代码详解、测试用例以及综合分析; 并提供Java、python、JavaScript、C++、C语言、GO六种语言的最佳实现方式! 本文收录于专栏:《2025华为OD真题目录+全流程解析/备考攻略/经验分享》 华为OD机试真题《生成…...
react与vue的渲染原理
vue:响应式驱动模板编译 (1)模板编译 将模板(.vue 文件或 HTML 模板)编译为 渲染函数(Render Function); (2)响应式依赖收集 初始化时,通过 Ob…...
我提出结构学习的思路,意图用结构学习代替机器学习
我提出结构学习的思路,意图用结构学习代替机器学习 1.机器学习的本质和缺点 机器学习的规律是设计算法、用数据训练算法、让算法学会产生正确的数据回答问题,其缺点在于,需要大规模训练数据和巨大算力还其次,机器学习不能产生智…...
Outbox模式:确保微服务间数据可靠交换的设计方案
https://debezium.io/blog/2019/02/19/reliable-microservices-data-exchange-with-the-outbox-pattern/ Outbox模式是一种在微服务架构中确保数据更改和消息/事件发布之间可靠性的设计模式。它解决了在更新数据库和发送消息这两个独立操作中可能出现的不一致问题(…...
数据可视化的定义和类型
数据可视化是一种将数据转换为图形或视觉表示的方法。想象一下,你面前有一堆数字和表格,看着这些,可能会让人头大。数据可视化就像是给这些枯燥的数字画上一幅画。它用图表、地图和各种有趣的图形,帮我们把难懂的数字变得容易看懂…...
sqlite-vec:谁说SQLite不是向量数据库?
sqlite-vec 是一个 SQLite 向量搜索插件,具有以零依赖、轻量级、跨平台和高效 KNN 搜索等优势,是本地化向量检索(例如 RAG)、轻量级 AI 应用以及边缘计算等场景的理想工具。 sqlite-vec 使用纯 C 语言实现,零外部依赖…...

Redis最佳实践——性能优化技巧之监控与告警详解
Redis 在电商应用的性能优化技巧之监控与告警全面详解 一、监控体系构建 1. 核心监控指标矩阵 指标类别关键指标计算方式/说明健康阈值(参考值)内存相关used_memoryINFO Memory 获取不超过 maxmemory 的 80%mem_fragmentation_ratio内存碎片率 used_m…...

R3GAN训练自己的数据集
简介 简介:这篇论文挑战了"GANs难以训练"的广泛观点,通过提出一个更稳定的损失函数和现代化的网络架构,构建了一个简洁而高效的GAN基线模型R3GAN。作者证明了通过合适的理论基础和架构设计,GANs可以稳定训练并达到优异…...
MATLAB实战:Arduino硬件交互项目方案
以下是一个使用MATLAB与Arduino进行硬件交互的项目方案,涵盖传感器数据采集和执行器控制。本方案使用MATLAB的Arduino硬件支持包,无需额外编写Arduino固件。 系统组成 硬件: Arduino Uno 温度传感器(如LM35) 光敏电…...
bert扩充或者缩小词表
在BERT模型中添加自己的词汇(pytorch版) - 知乎 输入 1. 扩充词表 替换bert词表中的【unused】 2. 缩小词表 因为要使用预训练的模型,词id不能变,词向量矩阵大小不变 要做的是将减少的那一部分词全部对应为unk,即可…...
什么是 TOML?
🛠 Rust 配置文件实战:TOML 语法详解与结构体映射( 在 Rust 中,Cargo.toml 是每个项目的心脏。它不仅定义了项目的名称、版本和依赖项,还使用了一种轻巧易读的配置语言:TOML。 本文将深入解析 TOML 的语法…...
git怎么合并两个分支
git怎么合并分支代码 注意: 第一步你得把当前分支合到远程分支去才能有下面的操作 另外我是将develop分支代码合并到release分支去 git 命令 查看本地所有分支 git branch切换分支 例如切换到release分支 git checkout release拉取代码 git pull up release 合并分支 …...
1.文件操作相关的库
一、filesystem(C17) 和 fstream 1.std::filesystem::path - cppreference.cn - C参考手册 std::filesystem::path 表示路径 构造函数: path( string_type&& source, format fmt auto_format ); 可以用string进行构造,也可以用string进行隐式类…...
Pytorch中一些重要的经典操作和简单讲解
Pytorch中一些重要的经典操作和简单讲解: 形状变换操作 reshape() / view() import torchx torch.randn(2, 3, 4) print(f"原始形状: {x.shape}")# reshape可以处理非连续张量 y x.reshape(6, 4) print(f"reshape后: {y.shape}")# view要求…...

【容器docker】启动容器kibana报错:“message“:“Error: Cannot find module ‘./logs‘
说明: 1、服务器数据盘挂了,然后将以前的数据用rsync拷贝过去,启动容器kibana服务,报错信息如下图所示: 2、可能是拷贝docker文件夹,有些文件没有拷贝过去,导致无论是给文件夹授权用户kibana或者…...
基于bp神经网络的adp算法
基于BP神经网络的ADP(自适应动态规划)小程序的MATLAB实现示例。这个小程序包含Actor网络和Critic网络,用于解决优化问题。 MATLAB代码示例 % 基于BP神经网络的ADP小程序 % 包含Actor网络和Critic网络% 定义网络结构 inputSize 2; % 输入层…...

C#里与嵌入式系统W5500网络通讯(4)
怎么样修改W5500里的socket收发缓冲区呢? 需要进行下面的工作,首先要了解socket缓冲区的作用,接着了解缓冲区的硬件资源, 最后就是要了解自己的需求,比如自己需要哪个socket的收发送缓冲区多大。 硬件的寄存器为: 这是 W5500 数据手册中关于 Sn_RXBUF_SIZE(Socket n …...

Spring boot集成milvus(spring ai)
服务器部署Milvus Run Milvus with Docker Compose (Linux) milvus版本可在docker-compose.yml中进行image修改 启动后,docker查看启动成功 spring boot集成milvus 参考了这篇文章 Spring AI开发RAG示例,理解RAG执行原理 但集成过程中遇到了一系列…...

Visual Studio+SQL Server数据挖掘
这里写自定义目录标题 工具准备安装Visual studio 2017安装SQL Server安装SQL Server Management Studio安装analysis service SSMS连接sql serverVisual studio新建项目数据源数据源视图挖掘结构部署模型设置挖掘预测 部署易错点 工具准备 Visual studio 2017 analysis servi…...
maven项目编译时复制xml到classes目录方案
maven项目编译时复制xml到classes目录方案 <resources><resource><!-- xml放在java目录下 --><directory>src/main/java</directory><includes><include>**/*.xml</include></includes></resource></resources…...

通过阿里云服务发送邮件
通过阿里云服务发送邮件 1. 整体描述2. 方案选择2.1 控制台发送2.2 API接口接入2.3 SMTP接口接入2.4 结论 3. 前期工作3.1 准备工作3.2 配置工作3.3 总结 4. 收费模式4.1 免费额度4.2 资源包4.3 按量付费 5. Demo开发5.1 选择SMTP服务器5.2 pom引用5.3 demo代码5.4 运行结果 6 …...

Vad-R1:通过从感知到认知的思维链进行视频异常推理
文章目录 速览摘要1 引言2 相关工作视频异常检测与数据集视频多模态大语言模型具备推理能力的多模态大语言模型 3 方法:Vad-R13.1 从感知到认知的思维链(Perception-to-Cognition Chain-of-Thought)3.2 数据集:Vad-Reasoning3.3 A…...