11package com .thealgorithms .graph ;
22
3- import java .util .*;
3+ import java .util .Collections ;
4+ import java .util .HashMap ;
5+ import java .util .HashSet ;
6+ import java .util .LinkedList ;
7+ import java .util .List ;
8+ import java .util .Map ;
9+ import java .util .Set ;
10+ import java .util .Stack ;
411
512/**
613 * An implementation of Hierholzer's Algorithm to find an Eulerian Path or Circuit in an undirected graph.
7- * This algorithm finds a trail in a graph that visits every edge exactly once.
8- * Think of it like solving a puzzle where you trace every line without lifting your pen .
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 .
916 *
1017 * Wikipedia: https://en.wikipedia.org/wiki/Eulerian_path#Hierholzer's_algorithm
1118 */
1219public class HierholzerAlgorithm {
1320
21+ private final int numVertices ;
1422 private final Map <Integer , LinkedList <Integer >> graph ;
1523
1624 /**
17- * Sets up the algorithm with the graph we want to solve .
25+ * Constructor for the algorithm.
1826 * @param graph The graph represented as an adjacency list.
27+ * Using a LinkedList for neighbors is efficient for edge removal.
1928 */
2029 public HierholzerAlgorithm (Map <Integer , LinkedList <Integer >> graph ) {
21- this .graph = (graph == null ) ? new HashMap <>() : 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 ();
2237 }
2338
2439 /**
25- * Before starting, we have to ask: can this puzzle even be solved?
26- * This method checks the two essential rules for an undirected graph.
27- * @return true if a circuit is possible, false otherwise.
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.
2844 */
2945 public boolean hasEulerianCircuit () {
3046 if (graph .isEmpty ()) {
31- return true ; // An empty puzzle is trivially solved .
47+ return true ; // An empty graph has an empty circuit .
3248 }
3349
34- // Rule 1: Every point must have an even number of lines connected to it.
35- // This ensures for every way in, there's a way out.
50+ // Check 1: All vertices must have an even degree.
3651 for (int vertex : graph .keySet ()) {
3752 if (graph .get (vertex ).size () % 2 != 0 ) {
38- return false ; // Found a point with an odd number of lines .
53+ return false ; // Found a vertex with an odd degree .
3954 }
4055 }
4156
42- // Rule 2: The drawing must be one single, connected piece.
43- // You can't have a separate, floating part of the puzzle.
44- return isCoherentlyConnected ();
57+ // Check 2: All vertices with edges must be connected.
58+ if (!isCoherentlyConnected ()) {
59+ return false ;
60+ }
61+
62+ return true ;
4563 }
4664
4765 /**
48- * This is the main event—finding the actual path .
49- * @return A list of points ( vertices) that make up the complete circuit .
66+ * Finds the Eulerian circuit .
67+ * @return A list of vertices representing the circuit, or an empty list if none exists .
5068 */
5169 public List <Integer > findEulerianCircuit () {
5270 if (!hasEulerianCircuit ()) {
53- // If the puzzle can't be solved, return an empty path.
5471 return Collections .emptyList ();
5572 }
5673
57- // We'll work on a copy of the graph so we don't destroy the original.
74+ // Create a copy of the graph to avoid modifying the original during traversal .
5875 Map <Integer , LinkedList <Integer >> tempGraph = new HashMap <>();
5976 for (Map .Entry <Integer , LinkedList <Integer >> entry : graph .entrySet ()) {
6077 tempGraph .put (entry .getKey (), new LinkedList <>(entry .getValue ()));
6178 }
6279
63- // 'currentPath' is our breadcrumb trail as we explore .
80+ // Data structures for the algorithm .
6481 Stack <Integer > currentPath = new Stack <>();
65- // 'circuit' is where we'll lay out the final, complete path.
6682 List <Integer > circuit = new LinkedList <>();
6783
68- // Find any point to start from.
69- int startVertex = graph .keySet ().stream ().findFirst ().orElse (-1 );
70- if (startVertex == -1 ) return Collections .emptyList ();
84+ // Find a starting vertex (any vertex with edges).
85+ int startVertex = -1 ;
86+ for (int vertex : tempGraph .keySet ()) {
87+ if (!tempGraph .get (vertex ).isEmpty ()) {
88+ startVertex = vertex ;
89+ break ;
90+ }
91+ }
92+
93+ if (startVertex == -1 ) {
94+ if (graph .isEmpty ()) {
95+ return Collections .emptyList ();
96+ }
97+ return Collections .singletonList (graph .keySet ().iterator ().next ()); // Graph with one isolated vertex.
98+ }
7199
72100 currentPath .push (startVertex );
73101
74102 while (!currentPath .isEmpty ()) {
75103 int currentVertex = currentPath .peek ();
76104
77- // If there's an unexplored hallway from our current location...
105+ // If the current vertex has unvisited edges
78106 if (tempGraph .containsKey (currentVertex ) && !tempGraph .get (currentVertex ).isEmpty ()) {
79- // ...let's go down it.
80- int nextVertex = tempGraph . get ( currentVertex ). pollFirst ();
81- // Erase the hallway behind us so we don't use it again.
107+ int nextVertex = tempGraph . get ( currentVertex ). pollFirst (); // Get a neighbor
108+
109+ // Remove the reverse edge as well (for undirected graph)
82110 tempGraph .get (nextVertex ).remove (Integer .valueOf (currentVertex ));
83- // Add the new location to our breadcrumb trail.
111+
112+ // Push the neighbor to the stack to continue the tour
84113 currentPath .push (nextVertex );
85114 } else {
86- // If we've hit a dead end, we're done with this part of the tour.
87- // We add our location to the final path and backtrack.
115+ // If "stuck" (no more edges), backtrack and add to the final circuit.
88116 circuit .add (0 , currentPath .pop ());
89117 }
90118 }
@@ -93,20 +121,37 @@ public List<Integer> findEulerianCircuit() {
93121 }
94122
95123 /**
96- * A helper to check if the graph is one single piece .
97- * It does a simple walk (DFS) starting from one point and checks if it can reach all other points .
124+ * Helper method to check if all vertices with a non-zero degree are connected .
125+ * Uses a simple traversal (DFS).
98126 */
99127 private boolean isCoherentlyConnected () {
100- if (graph .isEmpty ()) return true ;
128+ if (graph .isEmpty ()) {
129+ return true ;
130+ }
131+
101132 Set <Integer > visited = new HashSet <>();
102- int startNode = graph .keySet ().stream ().findFirst ().orElse (-1 );
103- if (startNode == -1 ) return true ;
133+ int startNode = -1 ;
134+
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 ;
139+ break ;
140+ }
141+ }
142+
143+ // If no edges in the graph, it's connected.
144+ if (startNode == -1 ) {
145+ return true ;
146+ }
104147
148+ // Perform DFS from the start node
105149 dfs (startNode , visited );
106150
151+ // Check if all vertices with edges were visited
107152 for (int vertex : graph .keySet ()) {
108153 if (!graph .get (vertex ).isEmpty () && !visited .contains (vertex )) {
109- return false ; // Found a part of the puzzle we couldn 't reach.
154+ return false ; // Found a vertex with edges that wasn 't visited
110155 }
111156 }
112157 return true ;
0 commit comments