99import java .util .Set ;
1010import java .util .Stack ;
1111
12- /**
13- * An implementation of Hierholzer's Algorithm to find an Eulerian Path or Circuit in an undirected graph.
14- * An Eulerian path is a trail in a graph that visits every edge exactly once.
15- * An Eulerian circuit is an Eulerian path that starts and ends at the same vertex.
16- *
17- * Wikipedia: https://en.wikipedia.org/wiki/Eulerian_path#Hierholzer's_algorithm
18- */
1912public class HierholzerAlgorithm {
2013
21- private final int numVertices ;
2214 private final Map <Integer , LinkedList <Integer >> graph ;
2315
24- /**
25- * Constructor for the algorithm.
26- * @param graph The graph represented as an adjacency list.
27- * Using a LinkedList for neighbors is efficient for edge removal.
28- */
2916 public HierholzerAlgorithm (Map <Integer , LinkedList <Integer >> graph ) {
30- if (graph == null ) {
31- this .graph = new HashMap <>();
32- this .numVertices = 0 ;
33- return ;
34- }
35- this .graph = graph ;
36- this .numVertices = graph .size ();
17+ this .graph = (graph == null ) ? new HashMap <>() : graph ;
3718 }
3819
39- /**
40- * Checks if an Eulerian circuit exists in the undirected graph.
41- * Condition: All vertices with a non-zero degree must be in a single connected component,
42- * and all vertices must have an even degree.
43- * @return true if a circuit exists, false otherwise.
44- */
4520 public boolean hasEulerianCircuit () {
4621 if (graph .isEmpty ()) {
47- return true ; // An empty graph has an empty circuit.
22+ return true ;
4823 }
4924
50- // Check 1: All vertices must have an even degree .
51- for (int vertex : graph .keySet ()) {
52- if (graph . get ( vertex ) .size () % 2 != 0 ) {
53- return false ; // Found a vertex with an odd degree.
25+ // FINAL FIX: Loop over values directly for efficiency .
26+ for (List < Integer > neighbors : graph .values ()) {
27+ if (neighbors .size () % 2 != 0 ) {
28+ return false ;
5429 }
5530 }
5631
57- // Check 2: All vertices with edges must be connected.
5832 if (!isCoherentlyConnected ()) {
5933 return false ;
6034 }
6135
6236 return true ;
6337 }
6438
65- /**
66- * Finds the Eulerian circuit.
67- * @return A list of vertices representing the circuit, or an empty list if none exists.
68- */
6939 public List <Integer > findEulerianCircuit () {
7040 if (!hasEulerianCircuit ()) {
7141 return Collections .emptyList ();
7242 }
7343
74- // Create a copy of the graph to avoid modifying the original during traversal.
7544 Map <Integer , LinkedList <Integer >> tempGraph = new HashMap <>();
7645 for (Map .Entry <Integer , LinkedList <Integer >> entry : graph .entrySet ()) {
7746 tempGraph .put (entry .getKey (), new LinkedList <>(entry .getValue ()));
7847 }
7948
80- // Data structures for the algorithm.
8149 Stack <Integer > currentPath = new Stack <>();
8250 List <Integer > circuit = new LinkedList <>();
8351
84- // Find a starting vertex (any vertex with edges).
8552 int startVertex = -1 ;
86- for (int vertex : tempGraph .keySet ()) {
87- if (!tempGraph .get (vertex ).isEmpty ()) {
88- startVertex = vertex ;
53+ // FINAL FIX: Use entrySet for efficiency.
54+ for (Map .Entry <Integer , LinkedList <Integer >> entry : tempGraph .entrySet ()) {
55+ if (!entry .getValue ().isEmpty ()) {
56+ startVertex = entry .getKey ();
8957 break ;
9058 }
9159 }
@@ -94,36 +62,26 @@ public List<Integer> findEulerianCircuit() {
9462 if (graph .isEmpty ()) {
9563 return Collections .emptyList ();
9664 }
97- return Collections .singletonList (graph .keySet ().iterator ().next ()); // Graph with one isolated vertex.
65+ return Collections .singletonList (graph .keySet ().iterator ().next ());
9866 }
9967
10068 currentPath .push (startVertex );
10169
10270 while (!currentPath .isEmpty ()) {
10371 int currentVertex = currentPath .peek ();
10472
105- // If the current vertex has unvisited edges
10673 if (tempGraph .containsKey (currentVertex ) && !tempGraph .get (currentVertex ).isEmpty ()) {
107- int nextVertex = tempGraph .get (currentVertex ).pollFirst (); // Get a neighbor
108-
109- // Remove the reverse edge as well (for undirected graph)
74+ int nextVertex = tempGraph .get (currentVertex ).pollFirst ();
11075 tempGraph .get (nextVertex ).remove (Integer .valueOf (currentVertex ));
111-
112- // Push the neighbor to the stack to continue the tour
11376 currentPath .push (nextVertex );
11477 } else {
115- // If "stuck" (no more edges), backtrack and add to the final circuit.
11678 circuit .add (0 , currentPath .pop ());
11779 }
11880 }
11981
12082 return circuit ;
12183 }
12284
123- /**
124- * Helper method to check if all vertices with a non-zero degree are connected.
125- * Uses a simple traversal (DFS).
126- */
12785 private boolean isCoherentlyConnected () {
12886 if (graph .isEmpty ()) {
12987 return true ;
@@ -132,26 +90,24 @@ private boolean isCoherentlyConnected() {
13290 Set <Integer > visited = new HashSet <>();
13391 int startNode = -1 ;
13492
135- // Find the first vertex with a degree greater than 0
136- for (int vertex : graph .keySet ()) {
137- if (!graph . get ( vertex ).isEmpty ()) {
138- startNode = vertex ;
93+ // FINAL FIX: Use entrySet for efficiency.
94+ for (Map . Entry < Integer , LinkedList < Integer >> entry : graph .entrySet ()) {
95+ if (!entry . getValue ( ).isEmpty ()) {
96+ startNode = entry . getKey () ;
13997 break ;
14098 }
14199 }
142100
143- // If no edges in the graph, it's connected.
144101 if (startNode == -1 ) {
145102 return true ;
146103 }
147104
148- // Perform DFS from the start node
149105 dfs (startNode , visited );
150106
151- // Check if all vertices with edges were visited
152- for (int vertex : graph .keySet ()) {
153- if (!graph . get ( vertex ).isEmpty () && !visited .contains (vertex )) {
154- return false ; // Found a vertex with edges that wasn't visited
107+ // FINAL FIX: Use entrySet for efficiency.
108+ for (Map . Entry < Integer , LinkedList < Integer >> entry : graph .entrySet ()) {
109+ if (!entry . getValue ( ).isEmpty () && !visited .contains (entry . getKey () )) {
110+ return false ;
155111 }
156112 }
157113 return true ;
0 commit comments