贪心算法应用:欧拉路径(Fleury算法)详解

news2025/7/24 1:59:39

在这里插入图片描述

Java中的贪心算法应用:欧拉路径(Fleury算法)详解

一、欧拉路径与欧拉回路基础

1.1 基本概念

欧拉路径(Eulerian Path)是指在一个图中,经过图中每一条边且每一条边只经过一次的路径。如果这条路径的起点和终点相同,则称为欧拉回路(Eulerian Circuit)。

1.2 存在条件

  • 无向图欧拉回路存在条件

    • 图是连通的
    • 所有顶点的度数都是偶数
  • 无向图欧拉路径存在条件

    • 图是连通的
    • 恰好有两个顶点的度数是奇数(这两个顶点分别是路径的起点和终点)
  • 有向图欧拉回路存在条件

    • 图是强连通的
    • 每个顶点的入度等于出度
  • 有向图欧拉路径存在条件

    • 图是弱连通的
    • 恰好有一个顶点的出度比入度大1(起点)
    • 恰好有一个顶点的入度比出度大1(终点)
    • 其他所有顶点的入度等于出度

二、Fleury算法原理

Fleury算法是一种用于在图中寻找欧拉路径或欧拉回路的贪心算法。它的核心思想是:尽可能不选择桥边(除非没有其他选择)。

2.1 算法步骤

  1. 检查图是否满足欧拉路径/回路的存在条件
  2. 选择起始顶点
    • 对于欧拉回路:可以选择任意顶点
    • 对于欧拉路径:必须选择奇数度数的顶点之一
  3. 递归/迭代选择边
    • 选择一条非桥边(除非没有其他选择)
    • 删除已选择的边
    • 移动到下一个顶点
  4. 重复上述过程直到所有边都被遍历

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;
    }
    
    // 从该顶点开始DFS
    dfs(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-v
    removeEdge(u, v);
    
    // 计算删除u-v后从u可到达的顶点数
    visited = new boolean[vertices];
    int count2 = dfsCount(u, visited);
    
    // 恢复边u-v
    addEdge(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();
}

四、算法复杂度分析

  1. 时间复杂度

    • 检查连通性(DFS):O(V + E)
    • 检查桥边:每次检查需要O(E)时间
    • 对于每条边,我们可能需要检查它是否是桥,因此总时间复杂度为O(E²)
  2. 空间复杂度

    • 主要取决于图的表示和递归深度
    • 邻接表:O(V + E)
    • 递归栈:最坏情况下O(E)

五、优化与改进

5.1 桥边检测优化

原始的Fleury算法在每一步都需要检测桥边,这导致了较高的时间复杂度。可以使用以下方法优化:

  1. 动态维护桥边信息:使用更高级的数据结构(如动态图算法)来维护桥边信息
  2. 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算法在实际中有多种应用:

  1. 邮路问题:邮递员如何走过所有街道且不重复
  2. DNA序列组装:将短DNA片段组装成完整序列
  3. 网络路由:设计网络数据包的路由路径
  4. 电路板钻孔:在电路板上钻孔的最优路径规划
  5. 笔画问题:判断图形是否可以一笔画成

七、常见问题与解决方案

7.1 如何处理多重图?

多重图(有重复边的图)需要特殊处理:

  • 邻接表中存储边时,可以使用带计数的结构
  • 删除边时减少计数而不是完全移除

7.2 如何处理极大图?

对于极大图,递归实现可能导致栈溢出:

  • 改用迭代实现
  • 增加栈大小(-Xss JVM参数)
  • 使用更高效的算法如Hierholzer

7.3 如何验证找到的路径确实是欧拉路径?

验证方法:

  1. 检查路径长度是否等于边数
  2. 检查每条边是否只出现一次
  3. 检查路径是否连续(相邻顶点间有边)

八、扩展与变种

  1. 中国邮路问题:允许重复经过某些边,求最短路径
  2. 有向图欧拉路径:调整算法处理有向边
  3. 加权图欧拉路径:在有权图中寻找最优欧拉路径
  4. 混合图欧拉路径:同时包含有向边和无向边的图

九、总结

Fleury算法是贪心算法在图论中的一个经典应用,它通过在每个步骤中尽可能选择非桥边来构建欧拉路径。虽然它的时间复杂度不是最优的,但其思路清晰,易于理解,是学习贪心算法和欧拉路径的绝佳范例。

Java实现时需要注意图的表示、连通性检查、桥边检测等关键点。对于实际应用,可以考虑使用更高效的算法如Hierholzer,但理解Fleury算法对于掌握贪心算法的应用场景和局限性非常有帮助。

欧拉路径问题及其解决方案在计算机科学和实际应用中都有着广泛的应用,掌握这些算法对于解决现实世界中的路径优化问题具有重要意义。

更多资源:

https://www.kdocs.cn/l/cvk0eoGYucWA

本文发表于【纪元A梦】!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2397850.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【算法设计与分析】实验——二维0-1背包问题(算法分析题:算法思路),独立任务最优调度问题(算法实现题:实验过程,描述,小结)

说明&#xff1a;博主是大学生&#xff0c;有一门课是算法设计与分析&#xff0c;这是博主记录课程实验报告的内容&#xff0c;题目是老师给的&#xff0c;其他内容和代码均为原创&#xff0c;可以参考学习&#xff0c;转载和搬运需评论吱声并注明出处哦。 要求&#xff1a;3-…

【Git】View Submitted Updates——diff、show、log

在 Git 中查看更新的内容&#xff08;即工作区、暂存区或提交之间的差异&#xff09;是日常开发中的常见操作。以下是常用的命令和场景说明&#xff1a; 文章目录 1、查看工作区与暂存区的差异2、查看提交历史中的差异3、查看工作区与最新提交的差异4、查看两个提交之间的差异5…

deepseek原理和项目实战笔记2 -- deepseek核心架构

混合专家&#xff08;MoE&#xff09; ​​混合专家&#xff08;Mixture of Experts, MoE&#xff09;​​ 是一种机器学习模型架构&#xff0c;其核心思想是通过组合多个“专家”子模型&#xff08;通常为小型神经网络&#xff09;来处理不同输入&#xff0c;从而提高模型的容…

在 MATLAB 2015a 中如何调用 Python

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

房屋租赁系统 Java+Vue.js+SpringBoot,包括房屋类型、房屋信息、预约看房、合同信息、房屋报修、房屋评价、房主管理模块

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

华为OD机试真题——生成哈夫曼树(2025B卷:100分)Java/python/JavaScript/C/C++/GO六种最佳实现

2025 B卷 100分 题型 本文涵盖详细的问题分析、解题思路、代码实现、代码详解、测试用例以及综合分析; 并提供Java、python、JavaScript、C++、C语言、GO六种语言的最佳实现方式! 本文收录于专栏:《2025华为OD真题目录+全流程解析/备考攻略/经验分享》 华为OD机试真题《生成…

Redis最佳实践——性能优化技巧之监控与告警详解

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

R3GAN训练自己的数据集

简介 简介&#xff1a;这篇论文挑战了"GANs难以训练"的广泛观点&#xff0c;通过提出一个更稳定的损失函数和现代化的网络架构&#xff0c;构建了一个简洁而高效的GAN基线模型R3GAN。作者证明了通过合适的理论基础和架构设计&#xff0c;GANs可以稳定训练并达到优异…

【容器docker】启动容器kibana报错:“message“:“Error: Cannot find module ‘./logs‘

说明&#xff1a; 1、服务器数据盘挂了&#xff0c;然后将以前的数据用rsync拷贝过去&#xff0c;启动容器kibana服务&#xff0c;报错信息如下图所示&#xff1a; 2、可能是拷贝docker文件夹&#xff0c;有些文件没有拷贝过去&#xff0c;导致无论是给文件夹授权用户kibana或者…

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修改 启动后&#xff0c;docker查看启动成功 spring boot集成milvus 参考了这篇文章 Spring AI开发RAG示例&#xff0c;理解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…

通过阿里云服务发送邮件

通过阿里云服务发送邮件 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 方法&#xff1a;Vad-R13.1 从感知到认知的思维链&#xff08;Perception-to-Cognition Chain-of-Thought&#xff09;3.2 数据集&#xff1a;Vad-Reasoning3.3 A…

黑马Java面试笔记之MySQL篇(事务)

一. 事务的特性 事务的特性是什么&#xff1f;可以详细说一下吗&#xff1f; 事务是一组操作的集合&#xff0c;他是一个不可分割的工作单位&#xff0c;事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求&#xff0c;即这些操作要么同时成功&#xff0c;要么同时失…

群辉(synology)NAS老机器连接出现网页端可以进入,但是本地访问输入一样的账号密码是出现错误时解决方案

群辉&#xff08;synology&#xff09;NAS老机器连接出现网页端可以进入&#xff0c;但是本地访问输入一样的账号密码是出现错误时解决方案 老机器 装的win7 系统 登入后端网页端的时候正常&#xff0c;但是本地访问登入时输入登入网页端一样的密码时候出现问题解决方案 1.登…

【深度学习】实验四 卷积神经网络CNN

实验四 卷积神经网络CNN 一、实验学时&#xff1a; 2学时 二、实验目的 掌握卷积神经网络CNN的基本结构&#xff1b;掌握数据预处理、模型构建、训练与调参&#xff1b;探索CNN在MNIST数据集中的性能表现&#xff1b; 三、实验内容 实现深度神经网络CNN。 四、主要实验步…

实现一个免费可用的文生图的MCP Server

概述 文生图模型为使用 Cloudflare Worker AI 部署 Flux 模型&#xff0c;是参照视频https://www.bilibili.com/video/BV1UbkcYcE24/?spm_id_from333.337.search-card.all.click&vd_source9ca2da6b1848bc903db417c336f9cb6b的复现Cursor MCP Server实现是参照文章https:/…

【手搓一个原生全局loading组件解决页面闪烁问题】

页面闪烁效果1 页面闪烁效果2 封装一个全局loading组件 class GlobalLoading extends HTMLElement {constructor() {super();this.attachShadow({ mode: open });}connectedCallback() {this.render();this.init();}render() {this.shadowRoot.innerHTML <style>.load…

CSS基础巩固-基础-选择

目录 CSS是如何工作的&#xff1f; 当浏览器遇到无法解析的CSS代码时 如何导入CSS样式&#xff1f; 改变元素的默认样式 选择 前缀符号&#xff08;后面会具体介绍&#xff09; 优先级 同时应用样式到多个类上 属性选择器 伪类 伪元素 关系选择器 后代选择器 子代…