欧拉回路详解

一、基本概念

⛺️欧拉路径(Eulerian Path)

在一个图中,经过图中每一条边且每一条边只经过一次的路径称为欧拉路径。

⛪️欧拉回路(Eulerian Circuit)

如果欧拉路径的起点和终点是同一个顶点,则称为欧拉回路。

二、判定条件 

🍓2.1无向图中的欧拉回路
一个无向图存在欧拉回路,当且仅当:

①图是连通的(不考虑度为0的孤立顶点)

②图中所有顶点的度数都是偶数

🫐2.2无向图中的欧拉路径(非回路)
一个无向图存在欧拉路径但不存在欧拉回路,当且仅当:

①图是连通

②图中恰好有两个顶点的度数是奇数(这两个顶点就是路径的起点和终点)

🥝2.3有向图中的欧拉回路
一个有向图存在欧拉回路,当且仅当:

①图是强连通的(从任一顶点可以到达其他所有顶点)

②每个顶点的入度等于出度

🍅2.4有向图中的欧拉路径(非回路)
一个有向图存在欧拉路径但不存在欧拉回路,当且仅当:

①图是弱连通的(不考虑边的方向时是连通的)

②恰好有一个顶点的出度比入度大1(起点)

③恰好有一个顶点的入度比出度大1(终点)

④其他所有顶点的入度等于出度

三、寻找欧拉回路的算法(Hierholzer算法)

步骤如下:

①选择起点:对于欧拉回路,可以选择任意顶点;对于欧拉路径,必须选择奇数度顶点(无向图)或出度比入度大1的顶点(有向图)

②深度优先遍历:从起点出发进行深度优先遍历,直到无法继续前进(即当前顶点的所有边都已被访问)

③构建回路:在无法继续前进时,将当前顶点加入回路中,并回溯到上一个顶点

④插入子回路:如果在回溯过程中发现有顶点还有未访问的边,则从该顶点开始新的遍历,将找到的子回路插入主回路中

⑤重复:直到所有边都被访问

四、相关概念扩展

🍞中国邮路问题:如果图中不存在欧拉回路,如何找到最短的覆盖所有边的闭合路径(允许重复边)

🥐哈密尔顿回路:经过每个顶点恰好一次的回路,与欧拉回路(经过每条边恰好一次)形成对比

🥖半欧拉图:存在欧拉路径但不一定存在欧拉回路的图

五、典型例题

【问题描述】

欧拉回路是指不令笔离开纸面,可画过图中每条边仅一次,且可以回到起点的一条回路。现给定一个图,问是否存在欧拉回路?

【输入形式】

测试输入包含若干测试用例。每个测试用例的第1行给出两个正整数,分别是节点数N ( 1 < N < 1000 )和边数M;随后的M行对应M条边,每行给出一对正整数,分别是该条边直接连通的两个节点的编号(节点从1到N编号)。当N为0时输入结束。

【输出形式】

每个测试用例的输出占一行,若欧拉回路存在则输出1,否则输出0。

【样例输入】

3 3 

1 2 

1 3 

2 3 

3 2 

1 2 

2 3 

0

【样例输出】

0

#include<bits/stdc++.h>  // 包含常用头文件
#define inf 0xffffff      // 定义无穷大常量
#define maxn 110000       // 定义最大顶点数
#include<queue>           // 包含队列头文件
using namespace std;

int pre[1100], d[1100];  // pre数组用于并查集,d数组记录每个顶点的度数
int m, n;                // m-边数,n-顶点数

// 初始化函数
void init()
{
    for(int i = 1; i <= n; i++)
        pre[i] = i;      // 初始化并查集,每个顶点是自己的父节点
    memset(d, 0, sizeof(d)); // 初始化度数数组为0
}

// 并查集查找函数(带路径压缩)
int find(int x)
{
    if(pre[x] == x)       // 如果x是自己的父节点
        return x;
    return find(pre[x]);  // 递归查找父节点
}

// 并查集合并函数
void join(int x, int y)
{
    int fx = find(x);     // 找到x的根节点
    int fy = find(y);     // 找到y的根节点
    if(fx != fy)          // 如果不在同一集合
        pre[x] = fy;      // 合并集合(这里可能有优化空间,应该pre[fx]=fy)
}

int main()
{
    int i, a, b;
    while(scanf("%d", &n), n)  // 输入顶点数n,n为0时结束
    {
        int ans = 0, cnt = 0, flag = 1; // ans-连通分量数,cnt-奇数度顶点数,flag-是否有欧拉回路标志
        init();                // 初始化
        
        scanf("%d", &m);      // 输入边数m
        for(i = 0; i < m; i++)
        {
            scanf("%d%d", &a, &b); // 输入每条边的两个顶点
            d[a]++;           // a顶点度数+1
            d[b]++;           // b顶点度数+1
            join(a, b);       // 合并a和b所在集合
        }
        
        // 检查图的连通性
        for(i = 1; i <= n; i++)
        {
            if(pre[i] == i)   // 统计根节点数量(连通分量数)
                ans++;
            if(ans > 1)       // 如果连通分量数大于1,图不连通
            {
                flag = 0;
                break;
            }
        }
        
        // 检查每个顶点的度数是否为偶数
        for(i = 1; i <= n; i++)
        {
            if(d[i] % 2 != 0) // 如果顶点度数为奇数
                cnt++;
            if(cnt > 0)        // 只要有一个奇数度顶点,就不满足欧拉回路条件
            {
                flag = 0;
                break;
            }
        }
        
        // 输出结果
        if(flag)             // 满足欧拉回路条件
            printf("1\n");
        else                 // 不满足欧拉回路条件
            printf("0\n");
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小瑾比个耶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值