Skip to content

欧拉图学习笔记

检测到 KaTeX 加载失败,可能会导致文中的数学公式无法正常渲染。

本文中提到的所有图均为连通图。

定义

  1. 通过图中所有边恰好一次的通路称为欧拉路径。
  2. 通过图中所有边恰好一次的回路称为欧拉回路。
  3. 具有欧拉回路的无向图或有向图称为欧拉图。
  4. 具有欧拉通路但不具有欧拉回路的无向图或有向图称为半欧拉图。

判定

有向图

  1. 存在欧拉路径的充分必要条件:
    • 所有点的出度均等于入度;或
    • 存在一个点的出度比入度多一(此时该点为起点),存在另一个点的入度比出度多一(此时该点为终点),其余点的入度和出度均相等。
  2. 存在欧拉回路的充分必要条件:所有点的入度均等于出度。

无向图

  1. 存在欧拉路径的充分必要条件:度数为奇数的点只能有 00 个或 22 个。
  2. 存在欧拉回路的充分必要条件:不能有度数为奇数的点。

求欧拉回路

从一个非孤立点开始 DFS,点可以重复经过,每次任意走一条边并将这条边从邻接表中删除(如果是无向图,其反向边也要被标记为删除),最终所有边一定会都会被经过。

DFS 回溯时记录每一条边,最终将记录的逆序即为欧拉回路。

代码

对应题目 UOJ #117. 欧拉回路

#include <iostream>
#include <cstring>

using std::cin;
using std::cout;
const char endl = '\n';

const int N = 1e5 + 5,
          M = 2e5 + 5;

int n, m, cnt, ans[M];
int idx, head[N], edge[M], next[M];
int din[N], dout[N];
bool vis[M];

void add(int u, int v) {
    next[idx] = head[u];
    edge[idx] = v;
    head[u] = idx++;
}

void dfs(int u) {
    for (int &i = head[u]; ~i;) {
        if (vis[i]) {
            i = next[i];
            continue;
        }

        int v = edge[i],
            x = i + 1;

        vis[i] = true;
        i = next[i];

        dfs(v);

        ans[++cnt] = x;
    }
}

int main() {
    std::ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n >> m;

    memset(head, 0xff, sizeof(head));

    for (int i = 1, u, v; i <= m; i++) {
        cin >> u >> v;

        add(u, v);

        dout[u]++, din[v]++;
    }

    for (int i = 1; i <= n; i++) {
        if (din[i] != dout[i]) {
            cout << "NO" << endl;

            exit(0);
        }
    }

    for (int i = 1; i <= n; i++) {
        if (~head[i]) {
            dfs(i);

            break;
        }
    }

    if (cnt < m) {  // 没有经过所有边
        cout << "NO" << endl;

        exit(0);
    }

    cout << "YES" << endl;

    for (int i = cnt; i; i--) {
        cout << ans[i] << ' ';
    }

    cout << endl;

    return 0;
}
#include <iostream>
#include <cstring>

using std::cin;
using std::cout;
const char endl = '\n';

const int N = 1e5 + 5,
          M = 2e5 + 5;

int n, m, cnt, ans[M];
int idx, head[N], edge[M << 1], next[M << 1];
int deg[N];
bool vis[M << 1];

void add(int u, int v) {
    next[idx] = head[u];
    edge[idx] = v;
    head[u] = idx++;
}

void dfs(int u) {
    for (int &i = head[u]; ~i;) {
        if (vis[i]) {
            i = next[i];
            continue;
        }

        int v = edge[i],
            x = i & 1 ? -(i >> 1) - 1 : (i >> 1) + 1;

        vis[i] = true;
        vis[i ^ 1] = true;
        i = next[i];

        dfs(v);

        ans[++cnt] = x;
    }
}

int main() {
    std::ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n >> m;

    memset(head, 0xff, sizeof(head));

    for (int i = 1, u, v; i <= m; i++) {
        cin >> u >> v;

        add(u, v);
        add(v, u);

        deg[u]++, deg[v]++;
    }

    for (int i = 1; i <= n; i++) {
        if (deg[i] & 1) {
            cout << "NO" << endl;

            exit(0);
        }
    }

    for (int i = 1; i <= n; i++) {
        if (~head[i]) {
            dfs(i);

            break;
        }
    }

    if (cnt < m) {  // 没有经过所有边
        cout << "NO" << endl;

        exit(0);
    }

    cout << "YES" << endl;

    for (int i = cnt; i; i--) {
        cout << ans[i] << ' ';
    }

    cout << endl;

    return 0;
}

参考资料

  1. 3.10 欧拉回路和欧拉路径,AcWing 算法提高课,闫学灿,2019 年 12 月 7 日。
  2. 欧拉回路学习笔记,黄浩睿,2017 年 1 月 1 日。