欧拉回路算法

news2024/10/7 20:35:45

1 基本概念

1.1 欧拉路径和欧拉回路

欧拉路径:欧拉路是指从图中任意一个点开始到图中任意一个点结束的路径,并且图中每条边通过的且只通过一次
欧拉回路:欧拉回路是指起点和终点相同的欧拉路。
注意:如果欧拉回路,那么一定存在欧拉路径

注意: 是每条边被访问一次节点可能会被访问两次。

充分必要条件:
对于无向图,所有边都是连通的

(1)存在欧拉路径的充分必要条件:

  • 度数为奇数的点只能是0个或者2个

(2)存在欧拉回路的充分必要条件:

  • 度数为奇数的只能是0个

对于有向图,所有边都是连通的

(1)存在欧拉路径的充分必要条件:

  • 要么所有点的出度均等于入度。
  • 要么除了两个点之外,其余所有点的出度等于入度,剩余的两个点:一个满足出度比入度多1(起点),另一个满足入度比出度多1(终点)。

(2)存在欧拉回路的充分必要条件:

  • 所有点的出度均等于入度

2 欧拉路径判定算法

2.1 Fleury(弗罗莱) 算法

Fleury算法用来判断图是否是欧拉路径或欧拉回路的算法。
使用如下的欧拉图,了解Fleury算法的主要步骤。
在这里插入图片描述

  • 选节点1为起点,并将该起点加入路径中。Fleury算法选择存储欧拉路径。
    在这里插入图片描述
  • 从起点开始,一路DFS试着走出一条通路。方法是找与此节点相邻的节点。
    如果只有一个节点,则将这个点直接加入路径中。
    如果有多个相邻节点,则选择其中一条边,把相邻节点加入路径后,且删除这一条边。
    如果没有邻接节点,则从路径中弹出
    节点5和节点2都与1相邻,可以选择向5方向,也可以选择2方向。这里选择2方向,把节点2放入路径,然后置1-2这条边为删除状态。如此这般,一路经过3、4、5节点后回到1号节点。下图中标记为红色的边表示已经访问或被删除。
    在这里插入图片描述
  • 重新回到节点1,此时不再存在与节点1邻接的节点,从路径中弹也,依次可弹出5、4、3。直到碰到2号节点。
    在这里插入图片描述
  • 因为存在与2号节点邻接的节点,再次以2号节点为始点,使用DFS开路。一路上遇到6、7,且再次回到2号节点。
    在这里插入图片描述
  • 2号节点不存在与之邻接的节点,出栈。同理,7、6依次出栈。
    在这里插入图片描述
    小结:
    当有与当前节点邻接的节点时,一路DFS,直到没有邻接的尽头。些时,一轮DFS算法结束,从路径中依次弹出没有邻接节点的节点,直到遇到还有邻接节点的节点,新一轮的DFS重新开始。直到所有节点邻接的边全部访问完毕。
    编码实现:
#include <iostream>
#include <math.h>
#include <algorithm>
#include <cstring>
#include <stack>
#define INF 100000
using namespace std;
int graph[100][100];
int n,m;
stack<int> sta;
void read() {
	for(int i = 0; i < m; i++) {
		int f,t;
		cin >> f >> t;
		graph[f][t] = 1;
		graph[t][f] = 1;
	}
}
void dfs(int u) {
	sta.push(u);
	for(int i = 1; i <= n; i++) {
		if(graph[i][u] > 0) {
			//标记为删除
			graph[u][i] = 0;
			graph[i][u] = 0;
			dfs(i);
			//仅朝一条边方向 DFS,方便形成回路 
			break;
		}
	}
}
void fleury(int x) {
	int  isEdge;
	sta.push(x);
	while(!sta.empty()) {
		isEdge = 0;
		int t = sta.top();
		sta.pop();
		//检查是否有边
		for(int i = 1; i <= n; i++) {
			if(graph[t][i] > 0) {
				isEdge = 1;
				break;
			}
		}
		if(isEdge == 0) {
			//没有邻接边,输出
			cout << t << " ";
		} else {
			//有邻接边,一路DFS狂奔
			dfs(t);
		}
	}
}
int main() {
	cin >> n >> m;
	memset(graph,0,sizeof(graph));
	read();
	int num = 0;
	int start = 1;
	for(int i = 1; i <= n; i++) {
		int deg = 0;
		for(int j = 1; j <= n; j++)
			deg += graph[i][j];
		if(deg % 2 == 1) {
			//奇节点的数量
			start = i;
			num++;
		}
	}
	if(num == 0 || num == 2)
		fleury(start);
	else
		cout << "不存在欧拉路径" << endl;
	return 0;
}
//测试用例
7 8
1 2
1 5
2 3
2 6
2 7    
3 4
4 5
6 7    

测试结果
在这里插入图片描述

from typing import List
from collections import defaultdict


class Solution:
    def __init__(self):
        self.G = defaultdict(list)
        self.degree = defaultdict(int)
        self.res = []
        
    def dfs(self, start):
        for j in self.G[start]:
            if j in self.G[start]:
                self.G[start].remove(j)
                self.G[j].remove(start)
                self.dfs(j)
        self.res.append(start)


    def fleury(self, connects) -> List:
        for con in connects:
            self.G[con[0]].append(con[1])
            self.degree[con[0]] += 1
            self.G[con[1]].append(con[0])
            self.degree[con[1]] += 1
        start = connects[0][0]
        cnt = 0
        for k,v in self.degree.items():
            if v %2 != 0:
                cnt += 1
                start = k
        if cnt == 0 or cnt == 2:
            self.dfs(start)
        else:
            print("不存在欧拉路径")
            return []
        return self.res
        

if __name__ == "__main__":
    so = Solution()
    connects = [[1,2], [2,3], [3,4],[4,5],[5, 1],[2,6],[6,7],[7,2]]
    res = so.fleury(connects)
    print(res)
        

2.2 Hierholzer 算法

也称逐步插入回路法。由数学家卡尔·希尔霍尔策给出,基于贪心思想。Hierholzer 的基本思路。先找到一个子回路,以此子回路为基础,逐步将其它回路以插入的方式合并到该子回路中,最终形成完整的欧拉回路。继续使用上图做演示。

  • 寻找子回路:如下从节点1开始,沿着边遍历图,一边遍历一边删除经过的边。如果遇到一个所有边都被删除的节点,那么该节点必然是 1(回到初始点)。将该回路上的节点和边添加到结果序列中。这个过程和Fleury算法没有太多区别。
    在这里插入图片描述

  • 回溯时检查刚添加到结果序列中的节点,看是否还有与节点相连且未遍历的边。可发现节点 2 有未遍历的边,则从 2 出发开始遍历,找到一个包含2 的新回路,将结果序列中的一个 2 用这个新回路替换,此时结果序列仍然是一个回路。这是和Fleury算法最大区别。
    在这里插入图片描述

  • 重复直到所有边都被遍历。

编码实现:

#include<iostream>
#include<string.h>
#include<vector>

const int maxn = 10005;
const int maxm = 1000005;//edge
using namespace std;
int n,m;
struct Edge {
	int to, nxt;
	bool vis=0;
};
Edge edge[maxm];
//如果没有以 i 为起点的有向边则 head[i] 的值为 0
int head[maxm];
//边的个数
int cnt;
//存储找到的回路
vector<Edge> ans;
//起始点
int sn;

void init() {
	for(int i=1; i<=n; i++) {
		head[i]=0;
		cnt=0;
	}
}

/*
*添加边
*/
void addEdge(int from, int to) {
	edge[cnt].to = to;
	edge[cnt].nxt = head[from];
	head[from] = cnt++;
}
void read() {
	int f,t;
	for(int i=1; i<=m; i++) {
		cin>>f>>t;
		addEdge(f,t);
		addEdge(t,f);
	}
}
void hierholzer(int sn) {
	for (int i = head[sn]; i != 0; i = edge[i].nxt) {
		// 遍历过
		if (edge[i].vis) continue;
		// 删除
		edge[i].vis = edge[i ^ 1].vis = true;
		// 继续
		hierholzer(edge[i].to);
		// 回溯时加入结果序列后,循环会继续查找是否有邻接边
		ans.push_back(edge[i]);
        
	}
}
void show() {
	for(int i=0; i<ans.size(); i++) {
		cout<<ans[i].to<<"\t";
	}
	cout<<sn<<"\t";
}

int main() {
	cin>>n>>m;
	sn=1;
	init();
	read();
	hierholzer(sn);
	show();
	return 0;
}

测试结果:
在这里插入图片描述

3. 总结

Hierholzer和Fleury算法的基本思路差不多,在DFS时找环。Fleury使用分段策略,找到一条环后,以环中某一个还存在邻接边的节点重新开始使用DFS找环,直到找到所有环。Hierholzer算法很有技巧性,在回溯时检查节点是否还有邻接边,有则重新DFS直到完毕。

参考资料:
https://blog.csdn.net/y6123236/article/details/135020029
https://blog.csdn.net/binggui2/article/details/108540016

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

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

相关文章

基于51单片机的无线病床呼叫系统设计—LCD1602显示

基于51单片机的无线病床呼叫系统 &#xff08;仿真&#xff0b;程序&#xff0b;原理图&#xff0b;设计报告&#xff09; 功能介绍 具体功能&#xff1a; 1.病人按下按键&#xff0c;LCD1602显示对应的床位号&#xff1b; 2.多人同时呼叫&#xff0c;显示屏同时显示&#xf…

5、JVM-G1详解

G1收集器 -XX:UseG1GC G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征. G1将Java堆划分为多个大小相等的独立区域&#xff08;Region&#xff09;&#xff0c;JVM目标…

001vscode为什么设置不了中文?

VSCode中文插件安装 在VSCode中设置中文的首要步骤是安装“Chinese (Simplified) Language Pack for Visual Studio Code”扩展插件。这一过程十分简单&#xff0c;只需打开VSCode&#xff0c;进入扩展市场&#xff0c;搜索“ Chinese (Simplified) Language Pack ”然后点击…

C语言高质量编程之assert()和const

目录 编程中常见的错误 assert() const 编程中常见的错误 在编程中我们通常会遇到三种错误形式&#xff0c;分别是&#xff1a;编译型错误&#xff0c;链接型错误&#xff0c;运行时错误。 编译型错误&#xff1a; 在编译阶段发生的错误&#xff0c;绝大多数情况是由语法错误…

【Golang学习笔记】从零开始搭建一个Web框架(二)

文章目录 模块化路由前缀树路由 前情提示&#xff1a; 【Golang学习笔记】从零开始搭建一个Web框架&#xff08;一&#xff09;-CSDN博客 模块化路由 路由在kilon.go文件中导致路由和引擎交织在一起&#xff0c;如果要实现路由功能的拓展增强&#xff0c;那将会非常麻烦&…

第二期书生浦语大模型训练营第三次作业

任务一&#xff1a;在茴香豆 Web 版中创建自己领域的知识问答助手 构建个人回答助手 进入web页面&#xff0c;传输属于自己的文件&#xff0c;此处进行输入大量投资领域资料&#xff0c;构建个人投资者问答助手 回答示例 茴香豆缺陷 此处会发现茴香豆仍然存在一些缺点&#…

CF938Div3(A-F)

A: 买n个酸奶&#xff0c;一次一瓶a元,一次买两瓶可以优惠价b元,也可以a元,问恰好买n瓶需要多少钱. void solve() {int n, a, b;cin >> n >> a >> b;int ans min(a * n, n / 2 * b n % 2 * a);cout << ans << endl; } B: 给你一个数组,问能…

手把手教你安装深度学习框架PyTorch:一键式安装指南

随着人工智能和深度学习的飞速发展&#xff0c;PyTorch作为一个强大而灵活的深度学习框架&#xff0c;受到了越来越多研究者和开发者的青睐。PyTorch不仅易于上手&#xff0c;而且支持动态计算图&#xff0c;使得调试和实验变得非常方便。本文将手把手教你如何安装PyTorch&…

Spark-机器学习(1)什么是机器学习与MLlib算法库的认识

从这一系列开始&#xff0c;我会带着大家一起了解我们的机器学习&#xff0c;了解我们spark机器学习中的MLIib算法库&#xff0c;知道它大概的模型&#xff0c;熟悉并认识它。同时&#xff0c;本篇文章为个人spark免费专栏的系列文章&#xff0c;有兴趣的可以收藏关注一下&…

若依从0到1部署

服务器安装 MySQL8 Ubuntu 在 20.04 版本中&#xff0c;源仓库中 MySQL 的默认版本已经更新到 8.0&#xff0c;因此可以直接使用 apt-get 安装。 设置 apt 国内代理 打开 https://developer.aliyun.com/mirror/ 阿里云镜像站&#xff0c;找到适合自己的系统&#xff1a; 找…

实战--------部署搭建ELFK+zookeeper+kafka架构

目录 一、部署jdk环境 二、搭建Elasticsearch 三、搭建logstash 四、搭建kibana服务 五、搭建filebeat服务 六、搭建zookeeper与kafka服务 七、部署ELFKzookeeperkafka Filebeat/Fluentd&#xff1a;负责从各服务器节点上实时收集日志数据&#xff0c;Filebeat轻量级&am…

js学习总结

这里写目录标题 前情提要JavaScript书写位置1. 内部javaScript (不常用)2. 外部javaScript (常用)3.内联javaScript (常用) js中的输入和输出输出语法1. document.write()2. alert()3. console.log() 输入语法prompt() 前情提要 1. 在javaScript中的 分号 是可以省略的JavaScr…

8:系统开发基础--8.1:软件工程概述、8.2:软件开发方法 、8.3:软件开发模型、8.4:系统分析

转上一节&#xff1a; http://t.csdnimg.cn/G7lfmhttp://t.csdnimg.cn/G7lfm 课程内容提要&#xff1a; 8&#xff1a;知识点考点详解 8.1&#xff1a;软件工程概述 1.软件的生存周期 2.软件过程改进—CMM Capability Maturity Model能力成熟度模型 3.软件过程改进—CMMI—…

Niobe开发板OpenHarmony内核编程开发——事件标志

本示例将演示如何在Niobe Wifi IoT开发板上使用cmsis 2.0 接口使用事件标志同步线程 EventFlags API分析 osEventFlagsNew() /// Create and Initialize an Event Flags object./// \param[in] attr event flags attributes; NULL: default values./// \return e…

【C++]C/C++的内存管理

这篇博客将会带着大家解决以下几个问题 1. C/C内存分布 2. C语言中动态内存管理方式 3. C中动态内存管理 4. operator new与operator delete函数 5. new和delete的实现原理 6. 定位new表达式(placement-new) 1. C/C内存分布 我们先来看下面的一段代码和相关问题 int global…

Java代码基础算法练习-拆分一个三位数的个位、十位、百位-2024.04.14

任务描述&#xff1a;输入一个三位数&#xff0c;逆序输出这个三位数的个位、十位、百位对应的数字&#xff0c;用空格分开。 任务要求&#xff1a; 代码示例&#xff1a; package April_2024;import java.util.Scanner; public class a240414 {public static void main(Strin…

Guava里一些比较常用的工具

随着java版本的更新提供了越来越多的语法和工具来简化日常开发&#xff0c;但是我们一般用的比较早的版本所以体验不到。这时就用到了guava这个包。guava提供了很多方便的工具方法&#xff0c;solar框架就依赖了guava的16.0.1版本&#xff0c;这里稍微介绍下。 一、集合工具类…

从零全面认识 多线程

目录 1.基本概念 2.创建线程方式 2.1直接建立线程 2.2实现Runnable接口 3.3实现Callable接口 3.4 了解Future接口 Future模式主要角色及其作用 3.5实例化FutureTask类 3.实现线程安全 3.1定义 3.2不安全原因 3.3解决方案 3.4volatile与synchronized区别 3.5Lock与…

二百三十、MySQL——MySQL表的索引

1 目的 梳理一下目前MySQL维度表的索引情况&#xff0c;当然网上也有其他博客专门讲MySQL索引的&#xff0c;我这边只是梳理一下目前的索引状况而已 2单列索引 2.1 索引截图 2.2 建表语句 3 联合索引 3.1 索引截图 3.2 建表语句 4 参考的优秀博客 http://t.csdnimg.cn/ZF7…

使用LangChain和GPT-4,创建Pandas DataFrame智能体

大家好&#xff0c;数据分析和数据处理是数据科学领域每天都在进行的基本任务。高效和快速的数据转换对于提取有意义的见解和基于数据做出明智决策至关重要。其中最受欢迎的工具之一是Python库Pandas&#xff0c;它提供了一个功能强大的DataFrame工具&#xff0c;使用灵活直观的…