首页 > 其他分享 >欧拉回路

欧拉回路

时间:2025-01-06 20:44:30浏览次数:4  
标签:连通 int tot dfs 回路 欧拉

网络上关于求欧拉回路的线性算法的资料普遍缺少证明。本文将通过分析欧拉回路的性质推导出这一算法。

算法流程

基本的定义可以参考 Alex_Wei 的博客,本文不再赘述。

算法流程部分仅推导求无向图欧拉回路的算法,求有向图欧拉回路的算法的推导过程是类似的,更改一些对应术语即可。

显然图中的孤立点与欧拉回路无关,本小节假设要么图中不存在孤立点,要么整张图就是一个孤立点。

性质 1:无向图 \(G\) 是欧拉图,当且仅当图 \(G\) 连通,且 \(\forall u \in G\),\(2 \mid d(u)\)。

证明:必要性显然,下证充分性。

考虑归纳法。\(|E| = 0\) 时结论显然成立。假设 \(|E| < m\) 时结论成立,下证 \(|E| = m\) 时结论成立。

显然图中至少存在一个环(否则图将会是一棵树,树的叶子节点度数为 \(1\),矛盾)。删除环上的边后图可能分裂成若干个连通块,但每个连通块中至少存在一个环上的点,并且仍然满足 \(\forall u \in G\),\(2 \mid d(u)\)。由归纳假设,每个连通块都存在欧拉回路,用环把这些欧拉回路串起来即可得到原图的欧拉回路。

性质 2:若无向图 \(G\) 是欧拉图,则 \(G\) 是边双连通图。

证明:性质 1 的证明直接导出了这一结论。

性质 3:无向图 \(G\) 是半欧拉图,当且仅当图 \(G\) 连通,且 \(\sum_{u \in G} [2 \nmid d(u)] = 2\)。并且,设满足 \(2 \nmid d(u)\) 的两个点分别为 \(s, t\),则 \(G\) 的欧拉迹的两个端点一定为 \(s, t\)。

证明:必要性显然,下证充分性。

在 \(s, t\) 之间再连一条边,则图会变成一张欧拉图。把新图的欧拉回路中 \((s, t)\) 这条边删掉即可得到原图的一条欧拉迹。

现在考虑构造一张欧拉图的欧拉回路或一张半欧拉图的欧拉迹。考虑递归构造,定义函数 \(dfs(s)\) 返回 \(s\) 所在的连通块的生成子图中一条 \(s \rightsquigarrow s\) 的欧拉回路或一条 \(s \rightsquigarrow t\) 的欧拉迹。

若整张图就是一个孤立点,则可以返回一条空途径 \(s\)。否则任取 \(s\) 的一条邻边 \((s, v)\) 并删除,有以下两种情况:

  1. 图变得不连通,则 \((s, v)\) 是原图的一条割边。由性质 2,我们要求的一定是一条欧拉迹。并且,\(t\) 一定在 \(v\) 所在的连通块,因为假设在原图加入 \((s, t)\) 这条边则原图会变得连通。因此可以返回 \(dfs(s) + dfs(v)\)。
  2. 图仍然连通。可以返回 \(s + dfs(v)\)。

这样我们就得到了一个 \(O(m (n + m))\) 的算法,瓶颈在于判断图是否连通。

我们希望避免判断图是否连通,即合并两种情况。实际上这是非常简单的。可以先调用 \(dfs(v)\),设结果为 \(a\),再调用 \(dfs(s)\),设结果为 \(b\),最后返回 \(b + a\)。这是因为如果图不连通,则 \(dfs(s)\) 和 \(dfs(v)\) 的过程互不干扰,否则 \(dfs(v)\) 时会把图删空,\(dfs(s)\) 会直接返回 \(s\)。

这样我们就得到了一个 \(O(n + m)\) 的算法,不过实现时有一些需要注意的地方:

  • 删边可以用打标记 + 当前弧优化代替。
  • \(dfs\) 并不需要真的返回一条途径,只需要不断向构造的途径里 prepend 新的点。这可以通过在 \(dfs\) 过程中执行 append,最后执行一次 reverse 解决。
  • 不断 \(dfs(s)\) 其实很蠢,这相当于枚举 \(s\) 的每条出边并执行 \(dfs(v)\),回溯前再 append \(s\)。

这样就得到了我们平时实现的算法。

参考实现

模板题:UOJ117 欧拉回路。本题要求输出边的顺序,只需要把回溯前 append \(s\) 这一步改成每次调用 \(dfs(v)\) 之后 append \((s, v)\) 这条边即可。

#include <bits/stdc++.h>

using namespace std;

#define all(x) begin(x), end(x)
#define len(x) int((x).size())

using ll = long long;
using db = long double;

template <typename T>
inline bool ckmin(T &x, const T &y) {
    return y < x and (x = y, true);
}

template <typename T>
inline bool ckmax(T &x, const T &y) {
    return x < y and (x = y, true);
}

int n, m;

namespace undirected {
constexpr int N = 1e5 + 5, M = 4e5 + 5;

int tot = 1, hd[N], nxt[M], to[M], d[N];
bool vis[M];
vector<int> p;

void ae(int u, int v) {
    ++d[u], ++d[v];
    nxt[++tot] = hd[u], hd[u] = tot, to[tot] = v;
    nxt[++tot] = hd[v], hd[v] = tot, to[tot] = u;
}

void dfs(int u) {
    for (int &i = hd[u]; i > 0; i = nxt[i]) {
        if (vis[i / 2]) {
            continue;
        }
        vis[i / 2] = true;
        int tmp = i % 2 == 1 ? -(i / 2) : i / 2;
        dfs(to[i]);
        p.push_back(tmp);
    }
}

void proc() {
    for (int i = 1; i <= m; ++i) {
        int u, v;
        cin >> u >> v;
        ae(u, v);
    }
    int s = 0;
    for (int i = 1; i <= n; ++i) {
        if (d[i] % 2 == 1) {
            cout << "NO\n";
            return;
        }
        if (d[i] > 0) {
            s = i;
        }
    }
    if (s != 0) {
        dfs(s);
    }
    if (len(p) < m) {
        cout << "NO\n";
        return;
    }
    cout << "YES\n";
    for (int i = m; --i >= 0;) {
        cout << p[i] << " \n"[i == 0];
    }
}
}

namespace directed {
constexpr int N = 1e5 + 5, M = 2e5 + 5;

int tot, hd[N], nxt[M], to[M], inD[N], outD[N];
vector<int> p;

void ae(int u, int v) {
    ++inD[v], ++outD[u];
    nxt[++tot] = hd[u], hd[u] = tot, to[tot] = v;
}

void dfs(int u) {
    int &i = hd[u];
    while (i > 0) {
        int tmp = i;
        i = nxt[i];
        dfs(to[tmp]);
        p.push_back(tmp);
    }
}

void proc() {
    for (int i = 1; i <= m; ++i) {
        int u, v;
        cin >> u >> v;
        ae(u, v);
    }
    int s = 0;
    for (int i = 1; i <= n; ++i) {
        if (inD[i] != outD[i]) {
            cout << "NO\n";
            return;
        }
        if (inD[i] > 0) {
            s = i;
        }
    }
    if (s != 0) {
        dfs(s);
    }
    if (len(p) < m) {
        cout << "NO\n";
        return;
    }
    cout << "YES\n";
    for (int i = m; --i >= 0;) {
        cout << p[i] << " \n"[i == 0];
    }
}
}

int main() {
    cin.tie(nullptr)->sync_with_stdio(false);
    cin.exceptions(cin.failbit);

    int tc;
    cin >> tc >> n >> m;
    if (tc == 1) {
        undirected::proc();
    } else {
        directed::proc();
    }
}

标签:连通,int,tot,dfs,回路,欧拉
From: https://www.cnblogs.com/rain-city/p/-/eulerian-circuits

相关文章

  • openEuler欧拉部署Redis.240108
    一、系统优化关闭防火墙systemctlstopfirewalldsystemctldisablefirewalld关闭selinuxsed-ri's/SELINUX=enforcing/SELINUX=disabled/'/etc/selinux/configsetenforce0​二、安装Redisdnf-yinstallredisvim/etc/redis.conf#bind127.0.0.1bind0.0.0.0pr......
  • openEuler欧拉部署Harbor.240108
    ​一、系统优化关闭防火墙systemctlstopfirewalldsystemctldisablefirewalld二、安装Harborwgethttps://github.com/goharbor/harbor/releases/download/v2.8.1/harbor-offline-installer-v2.8.1.tgztarxvfharbor-offline-installer-v2.8.1.tgzdf-hmvharbor//ho......
  • openEuler欧拉使用sshpass不输入密码远程登录其他服务器.240108
    ​​ssh登陆不能在命令行中指定密码,sshpass的出现则解决了这一问题。用-p参数指定明文密码,然后直接登录远程服务器,它支持密码从命令行、文件、环境变量中读取。操作步骤:一、关闭防火墙systemctlstopfirewalldsystemctldisablefirewalld二、安装sshpassdnf-yinstall......
  • openEuler欧拉系统重置root密码.240108
    步骤:系统启动时,出现如下页面,按e进入内核编辑模式进入如下页面按下光标后,找到linux开头这一行,修改ro为rw,并在行尾添加init=/bin/sh,修改后效果如下,在crtl+x保存后开始进入如下页面执行修改密码操作,指令如下#修改root密码命令echo'87654321'|passwd--stdinr......
  • openEuler欧拉配置MySQL8的MGR单主双从.240108
    ​一、系统优化(三个节点全部操作)关闭防火墙systemctlstopfirewalldsystemctldisablefirewalld关闭selinuxecho"SELINUX=disabled">/etc/selinux/configecho"SELINUXTYPE=targeted">>/etc/selinux/configcat/etc/selinux/configsetenforce0设置......
  • openEuler欧拉安装Gitlab.240109
    1.安装GitLabwgethttps://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.rpm.shsudoos=eldist=8bash./script.rpm.shsudoEXTERNAL_URL="http://xxx.xxx.xx.xx"yuminstall-ygitlab-ce2.查启动状态,等待个二十来分钟gitlab-ctltail3.关......
  • openEuler-怎么看服务器操作系统是不是欧拉系统?.240109
    ​[root@localhost~]#cat/etc/os-releaseNAME="openEuler"VERSION="22.03(LTS-SP2)"ID="openEuler"VERSION_ID="22.03"PRETTY_NAME="openEuler22.03(LTS-SP2)"ANSI_COLOR="0;31"lsb_release-a有些发......
  • 欧拉OpenEuler下SSH或SCP免密连接配置方法.241230
    以下操作均在本地服务器上进行:一、生成公钥和私钥ssh-keygen-trsa二、将公钥复制到远程服务器ssh-copy-idusername@remote_server三、配置免密登录sshusername@remote_server四、虽然免密登录提高了工作效率和安全性,但也有一些注意事项需要牢记。首先,务必保护好你......
  • 欧拉OpenEuler安装MySQL8.241227
    1.安装mysqltar-xvfmysql-8.0.21-linux-glibc2.12-x86_64.tarmvmysql-8.0.21-linux-glibc2.12-x86_64/usr/local/mysql2.配置mysqlvim/etc/my.cnf[client]default-character-set=utf8mb4[mysqld]#nd-address=0.0.0.0port=3306user=mysqlbasedir=/usr/local/m......
  • 250103.openEuler欧拉安装Jenkins并修改构建workspace路径
    1.安装Jenkinswget-O/etc/yum.repos.d/jenkins.repohttps://pkg.jenkins.io/redhat-stable/jenkins.repo--no-check-certificaterpm--importhttps://pkg.jenkins.io/redhat-stable/jenkins.io-2023.keyyuminstall-yfontconfigjava-17-openjdkdnf-yinstalljenk......