先处理边,后记录点
dfs(u) 的任务不是“第一次访问点 u”,而是“把 u 能接上的未使用边全部处理完”。
结合“双环共享点 2”的例子,看懂任务暂停、回来继续、无边封口,以及为什么答案必须反转。
dfs(u) 的任务不是“第一次访问点 u”,而是“把 u 能接上的未使用边全部处理完”。
dfs(u) 调用 dfs(v) 时只是暂停。等 dfs(v) 完成,程序会回到原来的 dfs(u),继续找其他边。
对于节点 u,遍历它所有尚未使用的边;沿每条边递归 dfs。所有边处理完后,把 u 放进答案数组,最后反转答案。
dfs(v)push_back(u) 放在所有边遍历完以后目标通常是每个点访问一次,因此维护 visited[u]。
点可以重复经过,不能重复的是边,因此维护 used[边号]。
(u,v)dfs(v),原任务暂停answer.push_back(u)dfs(e.to);沿边进入新点。当前函数留在调用栈里,等子任务完成后继续执行自己的 while。
answer.push_back(u);它必须写在 while 外面:只有所有可用边都处理完,点 u 在最终路线中的位置才确定。
| 时刻 | 调用栈 | 倒序 answer | 真正发生的事 |
|---|---|---|---|
| 走入左环 | dfs(1), dfs(2), dfs(3), dfs(1) | 空 | 原来的 dfs(2) 暂停着,没有结束。 |
| 左环封口 | dfs(1), dfs(2) | 1, 3 | 回到 dfs(2),继续执行它的 while。 |
| 接上右环 | dfs(1), dfs(2), dfs(4), dfs(5), dfs(2) | 1, 3 | 共享点 2 发现还有边 2—4,于是自动拼入右环。 |
| 全部封口 | 空 | 1, 3, 2, 5, 4, 2, 1 | 这是逆序结果。 |
| 最后反转 | 空 | — | 1 → 2 → 4 → 5 → 2 → 3 → 1 |
visited[u] 禁止再次进入点 u?answer.push_back(u) 应该放在哪里?#include <bits/stdc++.h>
using namespace std;
struct Edge {
int to;
int id;
};
vector<vector<Edge> > graph;
vector<char> used;
vector<int> answer;
void dfs(int u) {
while (!graph[u].empty()) {
Edge e = graph[u].back();
graph[u].pop_back();
// 无向边在邻接表中存了两次,但只能使用一次
if (used[e.id]) {
continue;
}
used[e.id] = true;
dfs(e.to); // 有边:递归向前走
}
answer.push_back(u); // 无边:回退时才记点
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m;
cin >> n >> m;
graph.assign(n + 1, vector<Edge>());
used.assign(m, false);
vector<int> degree(n + 1, 0);
for (int id = 0; id < m; ++id) {
int u, v;
cin >> u >> v;
graph[u].push_back({v, id});
graph[v].push_back({u, id});
++degree[u];
++degree[v];
}
int start = -1;
int oddCount = 0;
for (int u = 1; u <= n; ++u) {
if (degree[u] % 2 == 1) {
++oddCount;
if (start == -1) start = u;
}
}
if (oddCount != 0 && oddCount != 2) {
cout << "No\n";
return 0;
}
// 欧拉回路:选择任意一个真正有边的点
if (start == -1) {
for (int u = 1; u <= n; ++u) {
if (degree[u] > 0) {
start = u;
break;
}
}
}
// 没有边时,这里约定输出点 1
if (m == 0) {
cout << 1 << '\n';
return 0;
}
dfs(start);
// 没走完所有边,说明有边的部分不连通
if ((int)answer.size() != m + 1) {
cout << "No\n";
return 0;
}
reverse(answer.begin(), answer.end());
for (int i = 0; i < (int)answer.size(); ++i) {
if (i > 0) cout << ' ';
cout << answer[i];
}
cout << '\n';
return 0;
}
used[e.id]graph[u].pop_back()answer.size() == m + 1不要求字典序时,每个邻接项只被弹出一次,时间复杂度为 O(V+E),空间复杂度为 O(V+E)。
递归深度最坏达到 O(E);边数很大时应改用显式栈版本。
visited[u] 禁止重复经过点。answer.push_back(u) 放进 while。E+1。