Skip to content
本博客自 2023 年 4 月 4 日起转为归档状态,可能不再发表新的博文。点此了解博主的竞赛生涯
This blog has been archived by the owner since April 4, 2023. It may no longer have new updates.

欧拉图学习笔记

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

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

#定义

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

#判定

#有向图

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

#无向图

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

#求欧拉回路

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

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

#代码

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

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#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;
}
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#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 日。