An Eulerian circuit uses every edge exactly once and returns; an Eulerian path does not need to return. Unlike the Hamiltonian (vertex) version, existence is a local degree condition and Hierholzer finds one in . Applications: de Bruijn sequences, route inspection (Chinese postman), balanced orientations, splitting bipartite graphs into matchings, and many constructive problems where “each item is used once” becomes “each edge is traversed once”.

UndirectedDirected
circuitall degrees even, edges in one component everywhere, edges in one weak component
pathexactly 0 or 2 odd vertices (start at an odd one)one vertex (start), one (end), others balanced, one weak component

Hierholzer

vector<int> eulerWalk(int n, const vector<pair<int, int>>& E, bool directed, int start) {   // vertex sequence, empty if none
    vector<vector<pair<int, int>>> g(n); vector<int> deg(n);
    for (int i = 0; i < sz(E); i++) {
        auto [u, v] = E[i]; g[u].push_back({v, i}); deg[u]++;
        if (directed) deg[v]--; else g[v].push_back({u, i}), deg[v]++;
    }
    int bad = 0;                                            // degree conditions for a trail starting at `start`
    for (int v = 0; v < n; v++) {
        if (directed) { if (deg[v] != 0 && !(v == start && deg[v] == 1) && deg[v] != -1) return {}; bad += deg[v] == -1; }
        else if (deg[v] % 2) { bad++; }
    }
    if (directed ? bad > 1 || (bad == 1) != (deg[start] == 1) : bad > 2 || (bad == 2 && deg[start] % 2 == 0)) return {};
    vector<char> used(sz(E)); vector<int> it(n), st{start}, res;
    while (sz(st)) {
        int u = st.back();
        while (it[u] < sz(g[u]) && used[g[u][it[u]].second]) it[u]++;
        if (it[u] == sz(g[u])) { res.push_back(u); st.pop_back(); }
        else { auto [v, id] = g[u][it[u]++]; used[id] = 1; st.push_back(v); }
    }
    reverse(all(res));
    if (sz(res) != sz(E) + 1) return {};                    // not all edges reached: disconnected
    return res;   // pick start = an odd vertex (undirected) or the vertex with out - in = +1 (directed) when one exists
}

Store edge ids to handle parallel edges and to output edges instead of vertices; the it[] pointer makes it linear. To output the sequence of edges, push the id together with the vertex.

Mark the edge id, not the vertex pair

With parallel edges, used[v] or used[{u,v}] is wrong — two distinct parallel edges between the same pair would be conflated. Always mark used[edge_id]; in an undirected graph both directions of an edge share one id, so marking it from either side is correct.

Chinese postman (route inspection)

Shortest closed walk using every edge at least once. Undirected: total weight + minimum-weight perfect matching of the odd-degree vertices under shortest-path distances (duplicate the matched paths, then everything is even). With odd vertices a bitmask DP that always matches the lowest unmatched vertex; otherwise weighted general matching (Weighted Matching). Directed: min-cost flow from vertices with excess in-degree to vertices with excess out-degree. Mixed graphs (some directed, some undirected edges): NP-hard.

ll chinesePostman(int n, const vector<array<ll, 3>>& E) {    // {u, v, w}; -1 if the edges are disconnected
    const ll INF = LLONG_MAX / 4; vector<vector<ll>> d(n, vector<ll>(n, INF)); vector<int> deg(n), p(n); iota(all(p), 0);
    auto f = [&](int x) { while (p[x] != x) x = p[x] = p[p[x]]; return x; };
    ll total = 0; for (int i = 0; i < n; i++) d[i][i] = 0;
    for (auto [u, v, w] : E) d[u][v] = min(d[u][v], w), d[v][u] = min(d[v][u], w), deg[u]++, deg[v]++, total += w, p[f(u)] = f(v);
    for (auto [u, v, w] : E) if (f(u) != f(E[0][0])) return -1;
    for (int k = 0; k < n; k++) for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
    vector<int> odd; for (int v = 0; v < n; v++) if (deg[v] % 2) odd.push_back(v);
    int k = sz(odd); vector<ll> dp(1 << k, INF); dp[0] = 0;
    for (int mask = 0; mask < (1 << k); mask++) {
        if (dp[mask] >= INF) continue;
        int i = 0; while (i < k && (mask >> i & 1)) i++;
        if (i == k) continue;
        for (int j = i + 1; j < k; j++) if (!(mask >> j & 1)) dp[mask | 1 << i | 1 << j] = min(dp[mask | 1 << i | 1 << j], dp[mask] + d[odd[i]][odd[j]]);
    }
    return total + dp[(1 << k) - 1];
}

De Bruijn sequences

A cyclic sequence over symbols containing every length- word exactly once has length ; it is an Eulerian circuit in the graph whose vertices are the words of length and whose edges are the length- words (every vertex has in- and out-degree ). The lexicographically smallest one is the concatenation, in lexicographic order, of all Lyndon words whose length divides (FKM algorithm):

vector<int> deBruijn(int k, int n) {                       // cyclic, length k^n, symbols 0..k-1
    vector<int> res, w{-1};
    while (sz(w)) {                                         // w runs over Lyndon words of length <= n in order
        w.back()++; int m = sz(w);
        if (n % m == 0) res.insert(res.end(), all(w));
        while (sz(w) < n) w.push_back(w[sz(w) - m]);
        while (sz(w) && w.back() == k - 1) w.pop_back();
    }
    return res;
}

As a linear string, append the first symbols again: length , optimal. For variants (forbidden words, fixed start) run Hierholzer on the de Bruijn graph instead. Genome assembly from short reads is exactly this: reads (or their overlaps) are the graph’s edges, and assembling the genome is an Eulerian path — the de Bruijn-graph approach, as opposed to the Hamiltonian overlap-layout-consensus approach, which is why de Bruijn assemblers scale and Hamiltonian ones don’t.

Counting Eulerian circuits: BEST theorem

For a connected balanced digraph, , where is the number of arborescences oriented into any fixed vertex (directed matrix-tree theorem, Matrix-Tree); circuits are counted up to rotation (fix the first edge out of ). Counting Eulerian circuits in undirected graphs is p-complete.

Euler tours as a construction tool

  • Balanced orientation: orient the edges of an undirected graph so that everywhere: join all odd vertices to a dummy vertex, take Euler circuits of each component, orient along them, drop the dummy edges.
  • Splitting into two halves: an Eulerian circuit of a graph with all even degrees, with edges alternately coloured, gives each vertex exactly half of its edges in each colour (for even-length circuits); this splits a -regular bipartite graph into two -regular ones, the basis of fast bipartite edge colouring (Colouring).
  • Minimum number of trails covering all edges of a connected graph: .
  • Grid / matrix construction problems (“each row and column balanced”, “arrange dominoes in a chain”, “sequence where each pair appears once”, “reconstruct an itinerary/route from a set of tickets or turn counts”): build the bipartite or line graph, check degrees, run Hierholzer.
  • The Königsberg bridges problem is the historical origin of this whole topic — see Seven Bridges of Königsberg for Euler’s original argument and the modern (post-1944, Kaliningrad) bridge count.
  • The degree-condition proof above is the archetype of an invariant/parity impossibility proof: find a quantity every move preserves, show the goal is incompatible with it. The same move proves the 15-puzzle unsolvable for odd permutations, the mutilated chessboard untileable, and the closed knight’s tour impossible on odd boards.