This page briefly introduces Eulerian graphs, their implementation, and their applications.
Definition
Only finite graphs are considered in this article.
In graph theory, an Eulerian path is a path that traverses every edge of a graph exactly once, while an Eulerian circuit is a circuit that traverses every edge exactly once. A graph containing an Eulerian circuit is called an Eulerian graph; a graph without an Eulerian circuit but with an Eulerian path is called a semi-Eulerian graph.
Warning
Although this definition uses the word “path”, the precise term here is trail. An Eulerian path or circuit uses every edge exactly once, but places no restriction on how often vertices are visited.
Properties
Assume below that the graph has no isolated vertices. This is without loss of generality, because the properties below remain true for the graph obtained by deleting the isolated vertices of , so the graph occurs explicitly twice.
For a connected graph , the following three properties are equivalent:
- is Eulerian;
- every vertex of has even degree (for a directed graph, every vertex has equal indegree and outdegree);
- is the edge-disjoint union of several cycles.
We prove the equivalence below.
If a graph is Eulerian, every vertex of has even degree. Start at any vertex and traverse one Eulerian circuit. The degree of a vertex is the number of departures from plus the number of arrivals at . Since the walk is a circuit, these two counts are equal for every vertex , so every degree has the form and is even. For a directed graph, the same argument shows that every vertex has equal indegree and outdegree.
Conversely, if every vertex of has even degree (or equal indegree and outdegree), then is the edge-disjoint union of cycles. Start at any vertex , choose an incident edge , move to , and delete . Continue until returning to . The walk must return to : whenever it reaches a new vertex , the preceding property leaves an odd number of unused incident edges, so the walk cannot stop at . Since has finitely many edges, the walk stops after finitely many steps and returns to , producing a cycle at . Deleting that cycle leaves a graph with the same property, so repeating the process decomposes the graph into edge-disjoint cycles. Moreover, each cycle can be split at repeatedly visited vertices into an edge-disjoint union of simple cycles, so “simple cycle” may be used in the statement above.
If a connected graph is the edge-disjoint union of cycles, then is Eulerian: repeatedly choose two cycles sharing a vertex and merge them into one cycle until no two cycles share a vertex. The final cycle is unique. For any two edge-disjoint cycles , if and share a vertex in they can be merged there. Otherwise choose on and on . Since is connected, there is a path joining and . Each lies on a cycle ; meets , meets , and meets (possibly ). Thus and can be merged through . The unique final cycle has edge set , so it is an Eulerian circuit of .
These properties also give a criterion for Eulerian graphs: a graph is Eulerian if and only if its nonzero-degree vertices are mutually (strongly) connected and all vertices have even degree (or equal indegree and outdegree).
A semi-Eulerian graph has exactly two odd-degree vertices, and these are the endpoints of an Eulerian path. Connecting those two vertices turns it into an Eulerian graph; deleting any edge from an Eulerian graph produces a semi-Eulerian graph. Thus, a graph is semi-Eulerian if and only if its nonzero-degree vertices are mutually (strongly) connected and exactly two vertices have odd degree. For a directed graph, the second condition is that there are exactly two vertices with , while all other vertices have equal indegree and outdegree.
Constructing Eulerian Circuits and Paths
We introduce the commonly used Hierholzer algorithm. Its central idea is the third Eulerian property above: an Eulerian graph is the edge-disjoint union of cycles. The proof already describes a complete procedure for merging edge-disjoint cycles into an Eulerian circuit. With a suitable data structure, such as a linked-list representation of cycles, the implementation is straightforward.
The algorithm first finds a cycle in the graph as the current cycle. Each time it chooses a vertex on the current cycle with unused incident edges, finds a new simple cycle starting there, and merges it into the current cycle. Once every vertex on the current cycle has no remaining incident edge, the current cycle is an Eulerian circuit.
The algorithm also applies to directed graphs. For a semi-Eulerian graph, start with a path joining the two odd-degree vertices, then repeatedly find a simple cycle at a vertex with unused edges and merge it into the current path; the result is an Eulerian path.
Implementation
The pseudocode for Hierholzer's algorithm is as follows:
Eulerian graph
The time complexity of Hierholzer's algorithm is .
The correctness proof above shows that finding a simple cycle (or the initial path of a semi-Eulerian graph) requires no backtracking: following unused edges always finds the desired cycle or path, and each edge is visited only once. To exploit this property, store edges in a linked-list-like structure, such as an adjacency list or forward-star representation, so that an edge is deleted immediately after it is visited. With a naive adjacency matrix, finding an edge costs each time, giving total complexity .
Note
In fact, the precise complexity is rather than , because the implementation can depend on edges rather than vertices and maintain one linked list of remaining edges.
To output the lexicographically smallest Eulerian path or circuit, sort the edges. The time complexity is , or with counting sort or radix sort.
Applications
Directed Eulerian graphs can be used for computer encoding.
Suppose there are letters. We want a circular disk with sectors, one letter per sector, such that every consecutive sectors spell a distinct string of length . After one complete rotation ( steps), we obtain the strings produced by the letters, letters long, all of them distinct.
Construct the following directed Eulerian graph:
Let and construct as follows:
Define the incidence of vertices and edges in as follows:
The vertex has outgoing edges: .
The edge enters the vertex .
This is connected, and every vertex has indegree equal to outdegree (both equal to ), so is a directed Eulerian graph.
In , find an Eulerian circuit . Take the last letter of each edge of and place the letters around the disk in the order in which the edges occur in .
Example
Luogu P2731 Fence Repair
Given an undirected graph with 500 vertices, find an Eulerian path or circuit. If several solutions exist, output the lexicographically smallest one.
The path or circuit need not visit every vertex.
The number of edges satisfies .
Solution
This problem is a direct application of Hierholzer's algorithm.
A std::stack<int> can store the answer, because the portion found when the result is not a circuit must be appended at the end.
Do not use an adjacency matrix, or the complexity will deteriorate to . Because the edges must be sorted, a forward-star representation or std::vector is recommended; the sample code uses std::vector.
Example code
#include <algorithm>
#include <iostream>
#include <stack>
#include <vector>
using namespace std;
struct edge {
int to;
bool exists;
int revref;
bool operator<(const edge& b) const { return to < b.to; }
};
vector<edge> beg[505];
int cnt[505];
constexpr int dn = 500;
stack<int> ans;
void Hierholzer(int x) { // core routine
for (int& i = cnt[x]; i < (int)beg[x].size();) {
if (beg[x][i].exists) {
edge e = beg[x][i];
beg[x][i].exists = beg[e.to][e.revref].exists = false;
++i;
Hierholzer(e.to);
} else {
++i;
}
}
ans.push(x);
}
int deg[505];
int reftop[505];
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
for (int i = 1; i <= dn; ++i) {
beg[i].reserve(1050); // reserve avoids dynamic allocation and improves speed
}
int m;
cin >> m;
for (int i = 1; i <= m; ++i) {
int a, b;
cin >> a >> b;
beg[a].push_back(edge{b, true, 0});
beg[b].push_back(edge{a, true, 0});
++deg[a];
++deg[b];
}
for (int i = 1; i <= dn; ++i) {
if (!beg[i].empty()) {
sort(beg[i].begin(), beg[i].end()); // sort for lexicographic greedy selection
}
}
for (int i = 1; i <= dn; ++i) {
for (int j = 0; j < (int)beg[i].size(); ++j) {
beg[i][j].revref = reftop[beg[i][j].to]++;
}
}
int bv = 0;
for (int i = 1; i <= dn; ++i) {
if (!deg[bv] && deg[i]) {
bv = i;
} else if (!(deg[bv] & 1) && (deg[i] & 1)) {
bv = i;
}
}
Hierholzer(bv);
while (!ans.empty()) {
cout << ans.top() << '\n';
ans.pop();
}
}