11/**
2- * Implementation of finding an Eulerian Path on a graph. This implementation verifies that the
3- * input graph is fully connected and supports self loops and repeated edges between nodes.
2+ * Implementation of finding an Eulerian Path on a directed graph. This implementation verifies that
3+ * the input graph is fully connected (all edges are reachable) and supports self loops and repeated
4+ * edges between nodes.
45 *
5- * <p>Test against: https://open.kattis.com/problems/eulerianpath
6- * http://codeforces.com/contest/508/problem/D
6+ * <p>An Eulerian Path is a path in a graph that visits every edge exactly once. An Eulerian Circuit
7+ * is an Eulerian Path which starts and ends on the same vertex.
78 *
8- * <p>Run: bazel run //src/main/java/com/williamfiset/algorithms/graphtheory:EulerianPathDirectedEdgesAdjacencyList
9+ * <p>Test against:
10+ * <ul>
11+ * <li>https://open.kattis.com/problems/eulerianpath
12+ * <li>http://codeforces.com/contest/508/problem/D
13+ * </ul>
914 *
10- * <p>Time Complexity: O(E)
15+ * <p>Run with:
16+ * bazel run //src/main/java/com/williamfiset/algorithms/graphtheory:EulerianPathDirectedEdgesAdjacencyList
1117 *
18+ * <p>Time Complexity: O(V + E)
19+ *
20+ * @see <a href="https://en.wikipedia.org/wiki/Eulerian_path">Eulerian Path (Wikipedia)</a>
1221 * @author William Fiset, william.alexandre.fiset@gmail.com
1322 */
1423package com .williamfiset .algorithms .graphtheory ;
2029
2130public class EulerianPathDirectedEdgesAdjacencyList {
2231
23- private final int n ;
24- private int edgeCount ;
25- private int [] in , out ;
26- private LinkedList <Integer > path ;
27- private List <List <Integer >> graph ;
28-
32+ private final int n ; // Number of nodes in the graph
33+ private int edgeCount ; // Number of edges in the graph
34+ private int [] in , out ; // Arrays to track in-degree and out-degree of each node
35+ private LinkedList <Integer > path ; // Stores the final Eulerian path
36+ private List <List <Integer >> graph ; // Adjacency list representation of the graph
37+
38+ /**
39+ * Initializes the solver with an adjacency list representation of a directed graph.
40+ *
41+ * @param graph Adjacency list where graph.get(i) contains the neighbors of node i
42+ */
2943 public EulerianPathDirectedEdgesAdjacencyList (List <List <Integer >> graph ) {
30- if (graph == null ) throw new IllegalArgumentException ("Graph cannot be null" );
31- n = graph .size ();
44+ if (graph == null ) {
45+ throw new IllegalArgumentException ("Graph cannot be null" );
46+ }
47+ this .n = graph .size ();
3248 this .graph = graph ;
33- path = new LinkedList <>();
49+ this . path = new LinkedList <>();
3450 }
3551
36- // Returns a list of edgeCount + 1 node ids that give the Eulerian path or
37- // null if no path exists or the graph is disconnected.
52+ /**
53+ * Finds an Eulerian path in the graph if one exists.
54+ *
55+ * <p>The algorithm first verifies the necessary conditions for an Eulerian path based on vertex
56+ * degrees and then uses Hierholzer's algorithm to construct the path via DFS.
57+ *
58+ * @return An array of node IDs representing the Eulerian path, or null if no path exists or the
59+ * graph is disconnected.
60+ *
61+ * <p>Time: O(V + E)
62+ * <p>Space: O(V + E)
63+ */
3864 public int [] getEulerianPath () {
3965 setUp ();
4066
41- if (!graphHasEulerianPath ()) return null ;
67+ if (!graphHasEulerianPath ()) {
68+ return null ;
69+ }
70+
71+ // Start DFS from a valid starting node
4272 dfs (findStartNode ());
4373
44- // Make sure all edges of the graph were traversed. It could be the
45- // case that the graph is disconnected in which case return null.
46- if (path .size () != edgeCount + 1 ) return null ;
74+ // Check if all edges were traversed. If the graph is disconnected
75+ // (excluding isolated nodes with no edges), path.size() will be less than edgeCount + 1.
76+ if (path .size () != edgeCount + 1 ) {
77+ return null ;
78+ }
4779
48- // Instead of returning the 'path' as a linked list return
49- // the solution as a primitive array for convenience.
80+ // Convert the path from LinkedList to a primitive array for the caller's convenience.
5081 int [] soln = new int [edgeCount + 1 ];
51- for (int i = 0 ; !path .isEmpty (); i ++) soln [i ] = path .removeFirst ();
82+ for (int i = 0 ; !path .isEmpty (); i ++) {
83+ soln [i ] = path .removeFirst ();
84+ }
5285
5386 return soln ;
5487 }
5588
89+ // Pre-computes in-degrees, out-degrees and the total edge count.
5690 private void setUp () {
57- // Arrays that track the in degree and out degree of each node.
5891 in = new int [n ];
5992 out = new int [n ];
60-
6193 edgeCount = 0 ;
6294
63- // Compute in and out node degrees.
6495 for (int from = 0 ; from < n ; from ++) {
6596 for (int to : graph .get (from )) {
6697 in [to ]++;
@@ -70,45 +101,87 @@ private void setUp() {
70101 }
71102 }
72103
104+ // A directed graph has an Eulerian path if and only if:
105+ // 1. At most one vertex has outDegree - inDegree = 1 (start node)
106+ // 2. At most one vertex has inDegree - outDegree = 1 (end node)
107+ // 3. All other vertices have inDegree == outDegree
73108 private boolean graphHasEulerianPath () {
74- if (edgeCount == 0 ) return false ;
109+ if (edgeCount == 0 ) {
110+ return false ;
111+ }
75112 int startNodes = 0 , endNodes = 0 ;
76113 for (int i = 0 ; i < n ; i ++) {
77- if (out [i ] - in [i ] > 1 || in [i ] - out [i ] > 1 ) return false ;
78- else if (out [i ] - in [i ] == 1 ) startNodes ++;
79- else if (in [i ] - out [i ] == 1 ) endNodes ++;
114+ int diff = out [i ] - in [i ];
115+ if (Math .abs (diff ) > 1 ) {
116+ return false ;
117+ } else if (diff == 1 ) {
118+ startNodes ++;
119+ } else if (diff == -1 ) {
120+ endNodes ++;
121+ }
80122 }
81123 return (endNodes == 0 && startNodes == 0 ) || (endNodes == 1 && startNodes == 1 );
82124 }
83125
126+ // Identifies a node to begin the Eulerian path traversal.
84127 private int findStartNode () {
85128 int start = 0 ;
86129 for (int i = 0 ; i < n ; i ++) {
87- // Unique starting node.
88- if (out [i ] - in [i ] == 1 ) return i ;
89- // Start at a node with an outgoing edge.
90- if (out [i ] > 0 ) start = i ;
130+ // If a node has one more outgoing edge than incoming, it MUST be the start.
131+ if (out [i ] - in [i ] == 1 ) {
132+ return i ;
133+ }
134+ // Otherwise, start at the first node encountered with at least one outgoing edge.
135+ if (out [i ] > 0 ) {
136+ start = i ;
137+ }
91138 }
92139 return start ;
93140 }
94141
95- // Perform DFS to find Eulerian path.
142+ /**
143+ * Recursive DFS implementation of Hierholzer's algorithm.
144+ *
145+ * <p>We traverse edges until we reach a node with no remaining outgoing edges, then backtrack.
146+ * During backtracking, we add the current node to the front of the path. This naturally merges
147+ * all sub-cycles into the main path.
148+ *
149+ * @param at The current node in the DFS traversal
150+ */
96151 private void dfs (int at ) {
97152 while (out [at ] != 0 ) {
153+ // Pick the next available edge. We decrement out[at] to "remove" the edge
154+ // and use it as an index to select the next neighbor. This is O(1) per edge.
98155 int next = graph .get (at ).get (--out [at ]);
99156 dfs (next );
100157 }
158+ // As we backtrack from the recursion, add nodes to the start of the path.
101159 path .addFirst (at );
102160 }
103161
104162 /* Graph creation helper methods */
105163
164+ /**
165+ * Initializes an empty adjacency list with n nodes.
166+ *
167+ * @param n The number of nodes in the graph
168+ * @return An empty adjacency list
169+ */
106170 public static List <List <Integer >> initializeEmptyGraph (int n ) {
107171 List <List <Integer >> graph = new ArrayList <>(n );
108- for (int i = 0 ; i < n ; i ++) graph .add (new ArrayList <>());
172+ for (int i = 0 ; i < n ; i ++) {
173+ graph .add (new ArrayList <>());
174+ }
109175 return graph ;
110176 }
111177
178+ /**
179+ * Adds a directed edge from one node to another.
180+ *
181+ * @param g Adjacency list to add the edge to
182+ * @param from The source node index
183+ * @param to The destination node index
184+ */
112185 public static void addDirectedEdge (List <List <Integer >> g , int from , int to ) {
113186 g .get (from ).add (to );
114187 }
@@ -137,11 +210,11 @@ private static void exampleFromSlides() {
137210 addDirectedEdge (graph , 5 , 6 );
138211 addDirectedEdge (graph , 6 , 3 );
139212
140- EulerianPathDirectedEdgesAdjacencyList solver ;
141- solver = new EulerianPathDirectedEdgesAdjacencyList (graph );
213+ EulerianPathDirectedEdgesAdjacencyList solver = new EulerianPathDirectedEdgesAdjacencyList (graph );
142214
143- // Outputs path: [1, 3, 5, 6, 3, 2, 4, 3, 1, 2, 2, 4, 6]
144- System .out .println (Arrays .toString (solver .getEulerianPath ()));
215+ // Expected path: [1, 3, 5, 6, 3, 2, 4, 3, 1, 2, 2, 4, 6]
216+ int [] path = solver .getEulerianPath ();
217+ System .out .println ("Path from slides: " + Arrays .toString (path ));
145218 }
146219
147220 private static void smallExample () {
@@ -155,10 +228,10 @@ private static void smallExample() {
155228 addDirectedEdge (graph , 2 , 1 );
156229 addDirectedEdge (graph , 4 , 1 );
157230
158- EulerianPathDirectedEdgesAdjacencyList solver ;
159- solver = new EulerianPathDirectedEdgesAdjacencyList (graph );
231+ EulerianPathDirectedEdgesAdjacencyList solver = new EulerianPathDirectedEdgesAdjacencyList (graph );
160232
161- // Outputs path: [0, 1, 4, 1, 2, 1, 3]
162- System .out .println (Arrays .toString (solver .getEulerianPath ()));
233+ // Expected path: [0, 1, 4, 1, 2, 1, 3]
234+ int [] path = solver .getEulerianPath ();
235+ System .out .println ("Small example path: " + Arrays .toString (path ));
163236 }
164237}
0 commit comments