欧拉路径与回路
概念讲解
基本概念
- 欧拉路径: 经过图中每条边恰好一次的路径
- 欧拉回路: 经过图中每条边恰好一次并回到起点的回路
判定条件
无向图
- 欧拉回路:所有顶点的度数都是偶数
- 欧拉路径:恰好有两个顶点的度数是奇数
有向图
- 欧拉回路:所有顶点的入度等于出度
- 欧拉路径:恰好有一个顶点出度比入度大 1,一个顶点入度比出度大 1
实现
Hierholzer 算法
123456789101112131415161718192021222324const int N = 100005;
vector<pair<int, int>> graph[N];
bool visited[N];
vector<int> path;
void dfs(int u) {
while (!graph[u].empty()) {
auto [v, id] = graph[u].back();
graph[u].pop_back();
if (!visited[id]) {
visited[id] = true;
dfs(v);
}
}
path.push_back(u);
}
vector<int> eulerPath(int start) {
dfs(start);
reverse(path.begin(), path.end());
return path;
}时间复杂度: O(E)
考点分析
初赛考查方式
- 选择题: 欧拉路径的判定
- 程序阅读: 理解欧拉路径算法
- 编程题: 欧拉路径应用
常见题型
- 欧拉路径判定
- 欧拉回路求解
例题精讲
例题 1:欧拉路径
题目: 求图中的欧拉路径。
解答:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051#include <iostream>
#include <vector>
#include <stack>
using namespace std;
const int N = 100005;
vector<int> graph[N];
int inDegree[N], outDegree[N];
vector<int> path;
void dfs(int u) {
while (!graph[u].empty()) {
int v = graph[u].back();
graph[u].pop_back();
dfs(v);
}
path.push_back(u);
}
int main() {
int n, m;
cin >> n >> m;
for (int i = 0; i < m; i++) {
int u, v;
cin >> u >> v;
graph[u].push_back(v);
inDegree[v]++;
outDegree[u]++;
}
// 找起点
int start = 1;
for (int i = 1; i <= n; i++) {
if (outDegree[i] - inDegree[i] == 1) {
start = i;
break;
}
}
dfs(start);
reverse(path.begin(), path.end());
for (int v : path) {
cout << v << " ";
}
cout << endl;
return 0;
}练习题
- 洛谷 P1341 无序字母对
查看答案
思路:将字母对看作边,求欧拉路径。
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 130;
vector<int> graph[N];
int inDegree[N], outDegree[N];
int parent[N];
int find(int x) {
return parent[x] == x ? x : parent[x] = find(parent[x]);
}
void dfs(int u) {
while (!graph[u].empty()) {
int v = graph[u].back();
graph[u].pop_back();
dfs(v);
}
cout << (char)u;
}
int main() {
int n;
cin >> n;
for (int i = 0; i < 128; i++) parent[i] = i;
for (int i = 0; i < n; i++) {
string s;
cin >> s;
int u = s[0], v = s[1];
graph[u].push_back(v);
outDegree[u]++;
inDegree[v]++;
parent[find(u)] = find(v);
}
// 找起点
int start = -1, end = -1;
bool valid = true;
for (int i = 0; i < 128; i++) {
if (outDegree[i] - inDegree[i] == 1) {
if (start != -1) { valid = false; break; }
start = i;
} else if (inDegree[i] - outDegree[i] == 1) {
if (end != -1) { valid = false; break; }
end = i;
} else if (inDegree[i] != outDegree[i]) {
valid = false;
break;
}
}
if (start == -1) {
for (int i = 0; i < 128; i++) {
if (outDegree[i] > 0) {
start = i;
break;
}
}
}
// 排序
for (int i = 0; i < 128; i++) {
sort(graph[i].begin(), graph[i].end());
}
dfs(start);
cout << endl;
return 0;
}- 洛谷 P2731 [USACO3.3] 骑马修栅栏
查看答案
思路:欧拉路径,注意起点选择。
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 505;
vector<int> graph[N];
int degree[N];
vector<int> path;
void dfs(int u) {
while (!graph[u].empty()) {
int v = graph[u].back();
graph[u].pop_back();
dfs(v);
}
path.push_back(u);
}
int main() {
int m;
cin >> m;
for (int i = 0; i < m; i++) {
int u, v;
cin >> u >> v;
graph[u].push_back(v);
graph[v].push_back(u);
degree[u]++;
degree[v]++;
}
// 排序
for (int i = 1; i <= 500; i++) {
sort(graph[i].begin(), graph[i].end());
}
// 找起点
int start = 1;
for (int i = 1; i <= 500; i++) {
if (degree[i] % 2 == 1) {
start = i;
break;
}
}
dfs(start);
reverse(path.begin(), path.end());
for (int v : path) {
cout << v << endl;
}
return 0;
}- 洛谷 P1127 词链
查看答案
思路:将单词看作边,首尾字母看作点,求欧拉路径。
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 26;
vector<pair<int, string>> graph[N];
int inDegree[N], outDegree[N];
vector<string> path;
void dfs(int u) {
while (!graph[u].empty()) {
auto [v, word] = graph[u].back();
graph[u].pop_back();
dfs(v);
path.push_back(word);
}
}
int main() {
int n;
cin >> n;
vector<string> words(n);
for (int i = 0; i < n; i++) {
cin >> words[i];
}
sort(words.begin(), words.end());
for (string& word : words) {
int u = word[0] - 'a';
int v = word.back() - 'a';
graph[u].push_back({v, word});
outDegree[u]++;
inDegree[v]++;
}
// 找起点
int start = -1, end = -1;
bool valid = true;
for (int i = 0; i < 26; i++) {
if (outDegree[i] - inDegree[i] == 1) {
if (start != -1) { valid = false; break; }
start = i;
} else if (inDegree[i] - outDegree[i] == 1) {
if (end != -1) { valid = false; break; }
end = i;
} else if (inDegree[i] != outDegree[i]) {
valid = false;
break;
}
}
if (start == -1) {
for (int i = 0; i < 26; i++) {
if (outDegree[i] > 0) {
start = i;
break;
}
}
}
dfs(start);
reverse(path.begin(), path.end());
for (int i = 0; i < path.size(); i++) {
if (i > 0) cout << ".";
cout << path[i];
}
cout << endl;
return 0;
}易错点
⚠️ 注意:
- 欧拉路径的判定条件很重要
- Hierholzer 算法要注意边的删除
- 欧拉路径和欧拉回路不同
- 注意图的连通性