欧拉路径与欧拉回路CSP-S

目录

💡 核心思想

欧拉路径是经过图中每条边恰好一次的路径。欧拉回路是起点和终点相同的欧拉路径。无向图中:所有点度数都是偶数 → 存在欧拉回路;恰好两个点度数为奇数 → 存在欧拉路径(从其中一个奇度点出发)。有向图中:所有点入度=出度 → 欧拉回路;恰好一个点出度=入度+1,一个点入度=出度+1 → 欧拉路径。

欧拉路径是图论中的经典问题,判断条件简单明了,但 Hierholzer 算法求具体路径需要仔细实现。很多同学能判断是否存在欧拉路径,但写不出正确的 DFS 求路径代码。关键技巧:用栈记录路径,每次走到死胡同就回溯入栈,最后逆序输出栈。

🎯 直觉理解

想象你在一个公园里散步,要求每条路都走一遍,但不能重复。如果所有路口都是偶数条路交汇,你可以从任何地方出发,最后回到起点(欧拉回路)。如果恰好有两个路口是奇数条路,你必须从其中一个出发,到另一个结束(欧拉路径)。这就是邮递员问题。

📝 算法流程

  1. 判断图是否连通
  2. 统计各点度数(无向图)或出入度(有向图)
  3. 判断是否存在欧拉路径/回路
  4. 用 Hierholzer 算法求路径:DFS 遍历每条未访问的边,走到死胡同时入栈,回溯
  5. 逆序输出栈即为欧拉路径

$$\text{无向图欧拉回路:} \forall v, \deg(v) \equiv 0 \pmod 2$$

$$\text{有向图欧拉回路:} \forall v, \text{in}(v) = \text{out}(v)$$

📊 复杂度分析

指标复杂度
时间$O(n + m)$
空间$O(n + m)$

💻 参考实现(C++)

C++ (C++17)
#include 
using namespace std;

// Hierholzer 算法求欧拉回路(无向图)
vector> g[10005]; // (邻居, 边编号)
bool used[20005];
stack path;

void euler(int u) {
    for (auto& [v, eid] : g[u]) {
        if (used[eid]) continue;
        used[eid] = true;
        euler(v);
    }
    path.push(u);
}

int main() {
    int n = 4, m = 4;
    // 建图:1-2, 2-3, 3-4, 4-1(一个环)
    g[1].push_back({2, 1}); g[2].push_back({1, 1});
    g[2].push_back({3, 2}); g[3].push_back({2, 2});
    g[3].push_back({4, 3}); g[4].push_back({3, 3});
    g[4].push_back({1, 4}); g[1].push_back({4, 4});
    
    euler(1);
    while (!path.empty()) {
        cout << path.top() << " ";
        path.pop();
    }
    cout << endl;
    return 0;
}

⚠️ 常见坑点

判断连通性时忽略孤立点——度数为 0 的点不影响欧拉路径,但要考虑

无向图中每条边存两次——used 数组要去重

Hierholzer 算法输出顺序——是逆序输出栈

欧拉路径 vs 欧拉回路的起点选择

📚 相关题目

题目来源难度备注
P2731 骑马修栅栏洛谷CSP-S欧拉路径模板题
P1341 无序字母对洛谷CSP-S欧拉路径 + 字典序最小
P7771 欧拉回路洛谷CSP-S有向图欧拉回路