From d3b0e3bf1a4bb27db36fd8773c5d5e3386866738 Mon Sep 17 00:00:00 2001 From: sharan Date: Fri, 10 Oct 2025 23:04:30 +0530 Subject: [PATCH 1/7] feat: Add Hierholzer's Algorithm for Eulerian Circuits --- .../graph/HierholzerAlgorithm.java | 125 ++++++++++++++++++ .../graph/HierholzerAlgorithmTest.java | 46 +++++++ 2 files changed, 171 insertions(+) create mode 100644 src/main/java/com/thealgorithms/graph/HierholzerAlgorithm.java create mode 100644 src/test/java/com/thealgorithms/graph/HierholzerAlgorithmTest.java diff --git a/src/main/java/com/thealgorithms/graph/HierholzerAlgorithm.java b/src/main/java/com/thealgorithms/graph/HierholzerAlgorithm.java new file mode 100644 index 000000000000..8f36e3377d6e --- /dev/null +++ b/src/main/java/com/thealgorithms/graph/HierholzerAlgorithm.java @@ -0,0 +1,125 @@ +package com.thealgorithms.graph; + +import java.util.*; + +/** + * An implementation of Hierholzer's Algorithm to find an Eulerian Path or Circuit in an undirected graph. + * This algorithm finds a trail in a graph that visits every edge exactly once. + * Think of it like solving a puzzle where you trace every line without lifting your pen. + * + * Wikipedia: https://en.wikipedia.org/wiki/Eulerian_path#Hierholzer's_algorithm + */ +public class HierholzerAlgorithm { + + private final Map> graph; + + /** + * Sets up the algorithm with the graph we want to solve. + * @param graph The graph represented as an adjacency list. + */ + public HierholzerAlgorithm(Map> graph) { + this.graph = (graph == null) ? new HashMap<>() : graph; + } + + /** + * Before starting, we have to ask: can this puzzle even be solved? + * This method checks the two essential rules for an undirected graph. + * @return true if a circuit is possible, false otherwise. + */ + public boolean hasEulerianCircuit() { + if (graph.isEmpty()) { + return true; // An empty puzzle is trivially solved. + } + + // Rule 1: Every point must have an even number of lines connected to it. + // This ensures for every way in, there's a way out. + for (int vertex : graph.keySet()) { + if (graph.get(vertex).size() % 2 != 0) { + return false; // Found a point with an odd number of lines. + } + } + + // Rule 2: The drawing must be one single, connected piece. + // You can't have a separate, floating part of the puzzle. + return isCoherentlyConnected(); + } + + /** + * This is the main event—finding the actual path. + * @return A list of points (vertices) that make up the complete circuit. + */ + public List findEulerianCircuit() { + if (!hasEulerianCircuit()) { + // If the puzzle can't be solved, return an empty path. + return Collections.emptyList(); + } + + // We'll work on a copy of the graph so we don't destroy the original. + Map> tempGraph = new HashMap<>(); + for (Map.Entry> entry : graph.entrySet()) { + tempGraph.put(entry.getKey(), new LinkedList<>(entry.getValue())); + } + + // 'currentPath' is our breadcrumb trail as we explore. + Stack currentPath = new Stack<>(); + // 'circuit' is where we'll lay out the final, complete path. + List circuit = new LinkedList<>(); + + // Find any point to start from. + int startVertex = graph.keySet().stream().findFirst().orElse(-1); + if (startVertex == -1) return Collections.emptyList(); + + currentPath.push(startVertex); + + while (!currentPath.isEmpty()) { + int currentVertex = currentPath.peek(); + + // If there's an unexplored hallway from our current location... + if (tempGraph.containsKey(currentVertex) && !tempGraph.get(currentVertex).isEmpty()) { + // ...let's go down it. + int nextVertex = tempGraph.get(currentVertex).pollFirst(); + // Erase the hallway behind us so we don't use it again. + tempGraph.get(nextVertex).remove(Integer.valueOf(currentVertex)); + // Add the new location to our breadcrumb trail. + currentPath.push(nextVertex); + } else { + // If we've hit a dead end, we're done with this part of the tour. + // We add our location to the final path and backtrack. + circuit.add(0, currentPath.pop()); + } + } + + return circuit; + } + + /** + * A helper to check if the graph is one single piece. + * It does a simple walk (DFS) starting from one point and checks if it can reach all other points. + */ + private boolean isCoherentlyConnected() { + if (graph.isEmpty()) return true; + Set visited = new HashSet<>(); + int startNode = graph.keySet().stream().findFirst().orElse(-1); + if (startNode == -1) return true; + + dfs(startNode, visited); + + for (int vertex : graph.keySet()) { + if (!graph.get(vertex).isEmpty() && !visited.contains(vertex)) { + return false; // Found a part of the puzzle we couldn't reach. + } + } + return true; + } + + private void dfs(int u, Set visited) { + visited.add(u); + if (graph.containsKey(u)) { + for (int v : graph.get(u)) { + if (!visited.contains(v)) { + dfs(v, visited); + } + } + } + } +} diff --git a/src/test/java/com/thealgorithms/graph/HierholzerAlgorithmTest.java b/src/test/java/com/thealgorithms/graph/HierholzerAlgorithmTest.java new file mode 100644 index 000000000000..ee479141994b --- /dev/null +++ b/src/test/java/com/thealgorithms/graph/HierholzerAlgorithmTest.java @@ -0,0 +1,46 @@ +package com.thealgorithms.graph; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.*; +import org.junit.jupiter.api.Test; + +public class HierholzerAlgorithmTest { + + @Test + public void testFindsEulerianCircuitInSimpleTriangleGraph() { + // Create a simple triangle graph where a circuit definitely exists: 0-1, 1-2, 2-0 + Map> graph = new HashMap<>(); + graph.put(0, new LinkedList<>(Arrays.asList(1, 2))); + graph.put(1, new LinkedList<>(Arrays.asList(0, 2))); + graph.put(2, new LinkedList<>(Arrays.asList(0, 1))); + + HierholzerAlgorithm algorithm = new HierholzerAlgorithm(graph); + + // Verify that the algorithm agrees a circuit exists + assertTrue(algorithm.hasEulerianCircuit()); + + List circuit = algorithm.findEulerianCircuit(); + + // A valid circuit for a triangle has 3 edges, so the path will have 4 vertices (e.g., 0 -> 1 -> 2 -> 0) + assertEquals(4, circuit.size()); + + // The path must start and end at the same vertex + assertEquals(circuit.get(0), circuit.get(circuit.size() - 1)); + } + + @Test + public void testHandlesGraphWithNoEulerianCircuit() { + // Create a graph where a vertex has an odd degree + Map> graph = new HashMap<>(); + graph.put(0, new LinkedList<>(Collections.singletonList(1))); + graph.put(1, new LinkedList<>(Collections.singletonList(0))); + graph.put(2, new LinkedList<>(Collections.emptyList())); // Vertex 2 is isolated + + HierholzerAlgorithm algorithm = new HierholzerAlgorithm(graph); + + // The algorithm should correctly identify that no circuit exists + assertEquals(false, algorithm.hasEulerianCircuit()); + } +} From 568913c6a62308d8f3b055c9b7629f590a9c3b96 Mon Sep 17 00:00:00 2001 From: sharan Date: Sat, 11 Oct 2025 11:54:34 +0530 Subject: [PATCH 2/7] fix: Add more test cases to improve code coverage --- .../graph/HierholzerAlgorithm.java | 119 ++++++++++++------ .../graph/HierholzerAlgorithmTest.java | 42 ++++++- 2 files changed, 119 insertions(+), 42 deletions(-) diff --git a/src/main/java/com/thealgorithms/graph/HierholzerAlgorithm.java b/src/main/java/com/thealgorithms/graph/HierholzerAlgorithm.java index 8f36e3377d6e..d1a849c36b57 100644 --- a/src/main/java/com/thealgorithms/graph/HierholzerAlgorithm.java +++ b/src/main/java/com/thealgorithms/graph/HierholzerAlgorithm.java @@ -1,90 +1,118 @@ package com.thealgorithms.graph; -import java.util.*; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Stack; /** * An implementation of Hierholzer's Algorithm to find an Eulerian Path or Circuit in an undirected graph. - * This algorithm finds a trail in a graph that visits every edge exactly once. - * Think of it like solving a puzzle where you trace every line without lifting your pen. + * An Eulerian path is a trail in a graph that visits every edge exactly once. + * An Eulerian circuit is an Eulerian path that starts and ends at the same vertex. * * Wikipedia: https://en.wikipedia.org/wiki/Eulerian_path#Hierholzer's_algorithm */ public class HierholzerAlgorithm { + private final int numVertices; private final Map> graph; /** - * Sets up the algorithm with the graph we want to solve. + * Constructor for the algorithm. * @param graph The graph represented as an adjacency list. + * Using a LinkedList for neighbors is efficient for edge removal. */ public HierholzerAlgorithm(Map> graph) { - this.graph = (graph == null) ? new HashMap<>() : graph; + if (graph == null) { + this.graph = new HashMap<>(); + this.numVertices = 0; + return; + } + this.graph = graph; + this.numVertices = graph.size(); } /** - * Before starting, we have to ask: can this puzzle even be solved? - * This method checks the two essential rules for an undirected graph. - * @return true if a circuit is possible, false otherwise. + * Checks if an Eulerian circuit exists in the undirected graph. + * Condition: All vertices with a non-zero degree must be in a single connected component, + * and all vertices must have an even degree. + * @return true if a circuit exists, false otherwise. */ public boolean hasEulerianCircuit() { if (graph.isEmpty()) { - return true; // An empty puzzle is trivially solved. + return true; // An empty graph has an empty circuit. } - // Rule 1: Every point must have an even number of lines connected to it. - // This ensures for every way in, there's a way out. + // Check 1: All vertices must have an even degree. for (int vertex : graph.keySet()) { if (graph.get(vertex).size() % 2 != 0) { - return false; // Found a point with an odd number of lines. + return false; // Found a vertex with an odd degree. } } - // Rule 2: The drawing must be one single, connected piece. - // You can't have a separate, floating part of the puzzle. - return isCoherentlyConnected(); + // Check 2: All vertices with edges must be connected. + if (!isCoherentlyConnected()) { + return false; + } + + return true; } /** - * This is the main event—finding the actual path. - * @return A list of points (vertices) that make up the complete circuit. + * Finds the Eulerian circuit. + * @return A list of vertices representing the circuit, or an empty list if none exists. */ public List findEulerianCircuit() { if (!hasEulerianCircuit()) { - // If the puzzle can't be solved, return an empty path. return Collections.emptyList(); } - // We'll work on a copy of the graph so we don't destroy the original. + // Create a copy of the graph to avoid modifying the original during traversal. Map> tempGraph = new HashMap<>(); for (Map.Entry> entry : graph.entrySet()) { tempGraph.put(entry.getKey(), new LinkedList<>(entry.getValue())); } - // 'currentPath' is our breadcrumb trail as we explore. + // Data structures for the algorithm. Stack currentPath = new Stack<>(); - // 'circuit' is where we'll lay out the final, complete path. List circuit = new LinkedList<>(); - // Find any point to start from. - int startVertex = graph.keySet().stream().findFirst().orElse(-1); - if (startVertex == -1) return Collections.emptyList(); + // Find a starting vertex (any vertex with edges). + int startVertex = -1; + for (int vertex : tempGraph.keySet()) { + if (!tempGraph.get(vertex).isEmpty()) { + startVertex = vertex; + break; + } + } + + if (startVertex == -1) { + if (graph.isEmpty()) { + return Collections.emptyList(); + } + return Collections.singletonList(graph.keySet().iterator().next()); // Graph with one isolated vertex. + } currentPath.push(startVertex); while (!currentPath.isEmpty()) { int currentVertex = currentPath.peek(); - // If there's an unexplored hallway from our current location... + // If the current vertex has unvisited edges if (tempGraph.containsKey(currentVertex) && !tempGraph.get(currentVertex).isEmpty()) { - // ...let's go down it. - int nextVertex = tempGraph.get(currentVertex).pollFirst(); - // Erase the hallway behind us so we don't use it again. + int nextVertex = tempGraph.get(currentVertex).pollFirst(); // Get a neighbor + + // Remove the reverse edge as well (for undirected graph) tempGraph.get(nextVertex).remove(Integer.valueOf(currentVertex)); - // Add the new location to our breadcrumb trail. + + // Push the neighbor to the stack to continue the tour currentPath.push(nextVertex); } else { - // If we've hit a dead end, we're done with this part of the tour. - // We add our location to the final path and backtrack. + // If "stuck" (no more edges), backtrack and add to the final circuit. circuit.add(0, currentPath.pop()); } } @@ -93,20 +121,37 @@ public List findEulerianCircuit() { } /** - * A helper to check if the graph is one single piece. - * It does a simple walk (DFS) starting from one point and checks if it can reach all other points. + * Helper method to check if all vertices with a non-zero degree are connected. + * Uses a simple traversal (DFS). */ private boolean isCoherentlyConnected() { - if (graph.isEmpty()) return true; + if (graph.isEmpty()) { + return true; + } + Set visited = new HashSet<>(); - int startNode = graph.keySet().stream().findFirst().orElse(-1); - if (startNode == -1) return true; + int startNode = -1; + + // Find the first vertex with a degree greater than 0 + for (int vertex : graph.keySet()) { + if (!graph.get(vertex).isEmpty()) { + startNode = vertex; + break; + } + } + + // If no edges in the graph, it's connected. + if (startNode == -1) { + return true; + } + // Perform DFS from the start node dfs(startNode, visited); + // Check if all vertices with edges were visited for (int vertex : graph.keySet()) { if (!graph.get(vertex).isEmpty() && !visited.contains(vertex)) { - return false; // Found a part of the puzzle we couldn't reach. + return false; // Found a vertex with edges that wasn't visited } } return true; diff --git a/src/test/java/com/thealgorithms/graph/HierholzerAlgorithmTest.java b/src/test/java/com/thealgorithms/graph/HierholzerAlgorithmTest.java index ee479141994b..f2daf82a272d 100644 --- a/src/test/java/com/thealgorithms/graph/HierholzerAlgorithmTest.java +++ b/src/test/java/com/thealgorithms/graph/HierholzerAlgorithmTest.java @@ -1,9 +1,15 @@ package com.thealgorithms.graph; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import java.util.*; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; import org.junit.jupiter.api.Test; public class HierholzerAlgorithmTest { @@ -31,16 +37,42 @@ public void testFindsEulerianCircuitInSimpleTriangleGraph() { } @Test - public void testHandlesGraphWithNoEulerianCircuit() { - // Create a graph where a vertex has an odd degree + public void testFailsForGraphWithOddDegreeVertices() { + // Create a graph where vertices 0 and 1 have an odd degree (1) Map> graph = new HashMap<>(); graph.put(0, new LinkedList<>(Collections.singletonList(1))); graph.put(1, new LinkedList<>(Collections.singletonList(0))); - graph.put(2, new LinkedList<>(Collections.emptyList())); // Vertex 2 is isolated HierholzerAlgorithm algorithm = new HierholzerAlgorithm(graph); // The algorithm should correctly identify that no circuit exists - assertEquals(false, algorithm.hasEulerianCircuit()); + assertFalse(algorithm.hasEulerianCircuit()); + // The find method should return an empty list + assertTrue(algorithm.findEulerianCircuit().isEmpty()); + } + + @Test + public void testFailsForDisconnectedGraph() { + // Create a graph with two separate triangles (0-1-2 and 3-4-5) + Map> graph = new HashMap<>(); + graph.put(0, new LinkedList<>(Arrays.asList(1, 2))); + graph.put(1, new LinkedList<>(Arrays.asList(0, 2))); + graph.put(2, new LinkedList<>(Arrays.asList(0, 1))); + graph.put(3, new LinkedList<>(Arrays.asList(4, 5))); + graph.put(4, new LinkedList<>(Arrays.asList(3, 5))); + graph.put(5, new LinkedList<>(Arrays.asList(3, 4))); + + HierholzerAlgorithm algorithm = new HierholzerAlgorithm(graph); + + // All degrees are even, but the graph is not connected, so no circuit exists + assertFalse(algorithm.hasEulerianCircuit()); + } + + @Test + public void testHandlesEmptyGraph() { + Map> graph = new HashMap<>(); + HierholzerAlgorithm algorithm = new HierholzerAlgorithm(graph); + assertTrue(algorithm.hasEulerianCircuit()); + assertTrue(algorithm.findEulerianCircuit().isEmpty()); } } From 456e0cfd6c57218c42103515860bbc62a746ec5b Mon Sep 17 00:00:00 2001 From: sharan Date: Sat, 11 Oct 2025 12:18:56 +0530 Subject: [PATCH 3/7] feat: Add Hierholzer's Algorithm for Eulerian Circuits --- .../graph/HierholzerAlgorithmTest.java | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/test/java/com/thealgorithms/graph/HierholzerAlgorithmTest.java b/src/test/java/com/thealgorithms/graph/HierholzerAlgorithmTest.java index f2daf82a272d..9c9f468c9bb4 100644 --- a/src/test/java/com/thealgorithms/graph/HierholzerAlgorithmTest.java +++ b/src/test/java/com/thealgorithms/graph/HierholzerAlgorithmTest.java @@ -1,22 +1,14 @@ package com.thealgorithms.graph; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; +import static org.junit.jupiter.api.Assertions.*; +import java.util.*; import org.junit.jupiter.api.Test; public class HierholzerAlgorithmTest { @Test public void testFindsEulerianCircuitInSimpleTriangleGraph() { - // Create a simple triangle graph where a circuit definitely exists: 0-1, 1-2, 2-0 + // A simple triangle graph where a circuit definitely exists: 0-1, 1-2, 2-0 Map> graph = new HashMap<>(); graph.put(0, new LinkedList<>(Arrays.asList(1, 2))); graph.put(1, new LinkedList<>(Arrays.asList(0, 2))); @@ -50,7 +42,7 @@ public void testFailsForGraphWithOddDegreeVertices() { // The find method should return an empty list assertTrue(algorithm.findEulerianCircuit().isEmpty()); } - + @Test public void testFailsForDisconnectedGraph() { // Create a graph with two separate triangles (0-1-2 and 3-4-5) From 96bd4fce9052d0a2959c43ad3f9f8bfbf5347b5e Mon Sep 17 00:00:00 2001 From: sharan Date: Sat, 11 Oct 2025 12:20:37 +0530 Subject: [PATCH 4/7] fix: Apply clang-format after merge --- .../graph/HierholzerAlgorithmTest.java | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/test/java/com/thealgorithms/graph/HierholzerAlgorithmTest.java b/src/test/java/com/thealgorithms/graph/HierholzerAlgorithmTest.java index 9c9f468c9bb4..f2daf82a272d 100644 --- a/src/test/java/com/thealgorithms/graph/HierholzerAlgorithmTest.java +++ b/src/test/java/com/thealgorithms/graph/HierholzerAlgorithmTest.java @@ -1,14 +1,22 @@ package com.thealgorithms.graph; -import static org.junit.jupiter.api.Assertions.*; -import java.util.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; import org.junit.jupiter.api.Test; public class HierholzerAlgorithmTest { @Test public void testFindsEulerianCircuitInSimpleTriangleGraph() { - // A simple triangle graph where a circuit definitely exists: 0-1, 1-2, 2-0 + // Create a simple triangle graph where a circuit definitely exists: 0-1, 1-2, 2-0 Map> graph = new HashMap<>(); graph.put(0, new LinkedList<>(Arrays.asList(1, 2))); graph.put(1, new LinkedList<>(Arrays.asList(0, 2))); @@ -42,7 +50,7 @@ public void testFailsForGraphWithOddDegreeVertices() { // The find method should return an empty list assertTrue(algorithm.findEulerianCircuit().isEmpty()); } - + @Test public void testFailsForDisconnectedGraph() { // Create a graph with two separate triangles (0-1-2 and 3-4-5) From 0fac8064010d92316a5f3fd80029981f46e4f930 Mon Sep 17 00:00:00 2001 From: sharan Date: Sat, 11 Oct 2025 12:37:59 +0530 Subject: [PATCH 5/7] fix: Apply all formatting, style, and efficiency fixes --- .../graph/HierholzerAlgorithm.java | 84 +++++-------------- .../graph/HierholzerAlgorithmTest.java | 18 ---- 2 files changed, 20 insertions(+), 82 deletions(-) diff --git a/src/main/java/com/thealgorithms/graph/HierholzerAlgorithm.java b/src/main/java/com/thealgorithms/graph/HierholzerAlgorithm.java index d1a849c36b57..e9f15cec1d82 100644 --- a/src/main/java/com/thealgorithms/graph/HierholzerAlgorithm.java +++ b/src/main/java/com/thealgorithms/graph/HierholzerAlgorithm.java @@ -9,52 +9,26 @@ import java.util.Set; import java.util.Stack; -/** - * An implementation of Hierholzer's Algorithm to find an Eulerian Path or Circuit in an undirected graph. - * An Eulerian path is a trail in a graph that visits every edge exactly once. - * An Eulerian circuit is an Eulerian path that starts and ends at the same vertex. - * - * Wikipedia: https://en.wikipedia.org/wiki/Eulerian_path#Hierholzer's_algorithm - */ public class HierholzerAlgorithm { - private final int numVertices; private final Map> graph; - /** - * Constructor for the algorithm. - * @param graph The graph represented as an adjacency list. - * Using a LinkedList for neighbors is efficient for edge removal. - */ public HierholzerAlgorithm(Map> graph) { - if (graph == null) { - this.graph = new HashMap<>(); - this.numVertices = 0; - return; - } - this.graph = graph; - this.numVertices = graph.size(); + this.graph = (graph == null) ? new HashMap<>() : graph; } - /** - * Checks if an Eulerian circuit exists in the undirected graph. - * Condition: All vertices with a non-zero degree must be in a single connected component, - * and all vertices must have an even degree. - * @return true if a circuit exists, false otherwise. - */ public boolean hasEulerianCircuit() { if (graph.isEmpty()) { - return true; // An empty graph has an empty circuit. + return true; } - // Check 1: All vertices must have an even degree. - for (int vertex : graph.keySet()) { - if (graph.get(vertex).size() % 2 != 0) { - return false; // Found a vertex with an odd degree. + // FINAL FIX: Loop over values directly for efficiency. + for (List neighbors : graph.values()) { + if (neighbors.size() % 2 != 0) { + return false; } } - // Check 2: All vertices with edges must be connected. if (!isCoherentlyConnected()) { return false; } @@ -62,30 +36,24 @@ public boolean hasEulerianCircuit() { return true; } - /** - * Finds the Eulerian circuit. - * @return A list of vertices representing the circuit, or an empty list if none exists. - */ public List findEulerianCircuit() { if (!hasEulerianCircuit()) { return Collections.emptyList(); } - // Create a copy of the graph to avoid modifying the original during traversal. Map> tempGraph = new HashMap<>(); for (Map.Entry> entry : graph.entrySet()) { tempGraph.put(entry.getKey(), new LinkedList<>(entry.getValue())); } - // Data structures for the algorithm. Stack currentPath = new Stack<>(); List circuit = new LinkedList<>(); - // Find a starting vertex (any vertex with edges). int startVertex = -1; - for (int vertex : tempGraph.keySet()) { - if (!tempGraph.get(vertex).isEmpty()) { - startVertex = vertex; + // FINAL FIX: Use entrySet for efficiency. + for (Map.Entry> entry : tempGraph.entrySet()) { + if (!entry.getValue().isEmpty()) { + startVertex = entry.getKey(); break; } } @@ -94,7 +62,7 @@ public List findEulerianCircuit() { if (graph.isEmpty()) { return Collections.emptyList(); } - return Collections.singletonList(graph.keySet().iterator().next()); // Graph with one isolated vertex. + return Collections.singletonList(graph.keySet().iterator().next()); } currentPath.push(startVertex); @@ -102,17 +70,11 @@ public List findEulerianCircuit() { while (!currentPath.isEmpty()) { int currentVertex = currentPath.peek(); - // If the current vertex has unvisited edges if (tempGraph.containsKey(currentVertex) && !tempGraph.get(currentVertex).isEmpty()) { - int nextVertex = tempGraph.get(currentVertex).pollFirst(); // Get a neighbor - - // Remove the reverse edge as well (for undirected graph) + int nextVertex = tempGraph.get(currentVertex).pollFirst(); tempGraph.get(nextVertex).remove(Integer.valueOf(currentVertex)); - - // Push the neighbor to the stack to continue the tour currentPath.push(nextVertex); } else { - // If "stuck" (no more edges), backtrack and add to the final circuit. circuit.add(0, currentPath.pop()); } } @@ -120,10 +82,6 @@ public List findEulerianCircuit() { return circuit; } - /** - * Helper method to check if all vertices with a non-zero degree are connected. - * Uses a simple traversal (DFS). - */ private boolean isCoherentlyConnected() { if (graph.isEmpty()) { return true; @@ -132,26 +90,24 @@ private boolean isCoherentlyConnected() { Set visited = new HashSet<>(); int startNode = -1; - // Find the first vertex with a degree greater than 0 - for (int vertex : graph.keySet()) { - if (!graph.get(vertex).isEmpty()) { - startNode = vertex; + // FINAL FIX: Use entrySet for efficiency. + for (Map.Entry> entry : graph.entrySet()) { + if (!entry.getValue().isEmpty()) { + startNode = entry.getKey(); break; } } - // If no edges in the graph, it's connected. if (startNode == -1) { return true; } - // Perform DFS from the start node dfs(startNode, visited); - // Check if all vertices with edges were visited - for (int vertex : graph.keySet()) { - if (!graph.get(vertex).isEmpty() && !visited.contains(vertex)) { - return false; // Found a vertex with edges that wasn't visited + // FINAL FIX: Use entrySet for efficiency. + for (Map.Entry> entry : graph.entrySet()) { + if (!entry.getValue().isEmpty() && !visited.contains(entry.getKey())) { + return false; } } return true; diff --git a/src/test/java/com/thealgorithms/graph/HierholzerAlgorithmTest.java b/src/test/java/com/thealgorithms/graph/HierholzerAlgorithmTest.java index f2daf82a272d..4dadb206d134 100644 --- a/src/test/java/com/thealgorithms/graph/HierholzerAlgorithmTest.java +++ b/src/test/java/com/thealgorithms/graph/HierholzerAlgorithmTest.java @@ -16,44 +16,29 @@ public class HierholzerAlgorithmTest { @Test public void testFindsEulerianCircuitInSimpleTriangleGraph() { - // Create a simple triangle graph where a circuit definitely exists: 0-1, 1-2, 2-0 Map> graph = new HashMap<>(); graph.put(0, new LinkedList<>(Arrays.asList(1, 2))); graph.put(1, new LinkedList<>(Arrays.asList(0, 2))); graph.put(2, new LinkedList<>(Arrays.asList(0, 1))); - HierholzerAlgorithm algorithm = new HierholzerAlgorithm(graph); - - // Verify that the algorithm agrees a circuit exists assertTrue(algorithm.hasEulerianCircuit()); - List circuit = algorithm.findEulerianCircuit(); - - // A valid circuit for a triangle has 3 edges, so the path will have 4 vertices (e.g., 0 -> 1 -> 2 -> 0) assertEquals(4, circuit.size()); - - // The path must start and end at the same vertex assertEquals(circuit.get(0), circuit.get(circuit.size() - 1)); } @Test public void testFailsForGraphWithOddDegreeVertices() { - // Create a graph where vertices 0 and 1 have an odd degree (1) Map> graph = new HashMap<>(); graph.put(0, new LinkedList<>(Collections.singletonList(1))); graph.put(1, new LinkedList<>(Collections.singletonList(0))); - HierholzerAlgorithm algorithm = new HierholzerAlgorithm(graph); - - // The algorithm should correctly identify that no circuit exists assertFalse(algorithm.hasEulerianCircuit()); - // The find method should return an empty list assertTrue(algorithm.findEulerianCircuit().isEmpty()); } @Test public void testFailsForDisconnectedGraph() { - // Create a graph with two separate triangles (0-1-2 and 3-4-5) Map> graph = new HashMap<>(); graph.put(0, new LinkedList<>(Arrays.asList(1, 2))); graph.put(1, new LinkedList<>(Arrays.asList(0, 2))); @@ -61,10 +46,7 @@ public void testFailsForDisconnectedGraph() { graph.put(3, new LinkedList<>(Arrays.asList(4, 5))); graph.put(4, new LinkedList<>(Arrays.asList(3, 5))); graph.put(5, new LinkedList<>(Arrays.asList(3, 4))); - HierholzerAlgorithm algorithm = new HierholzerAlgorithm(graph); - - // All degrees are even, but the graph is not connected, so no circuit exists assertFalse(algorithm.hasEulerianCircuit()); } From 1de236eaff9dda0a9efe6e3d54118c6af4934936 Mon Sep 17 00:00:00 2001 From: sharan Date: Mon, 13 Oct 2025 06:22:48 +0530 Subject: [PATCH 6/7] docs: Apply feedback and improve Javadoc --- .../graph/HierholzerAlgorithm.java | 38 +++++++++++++------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/thealgorithms/graph/HierholzerAlgorithm.java b/src/main/java/com/thealgorithms/graph/HierholzerAlgorithm.java index e9f15cec1d82..a804f77d7fa6 100644 --- a/src/main/java/com/thealgorithms/graph/HierholzerAlgorithm.java +++ b/src/main/java/com/thealgorithms/graph/HierholzerAlgorithm.java @@ -9,7 +9,28 @@ import java.util.Set; import java.util.Stack; -public class HierholzerAlgorithm { +/** + * Implementation of Hierholzer's algorithm to find an Eulerian Circuit in an undirected graph. + *

+ * An Eulerian circuit is a trail in a graph that visits every edge exactly once, + * starting and ending at the same vertex. This algorithm finds such a circuit if one exists. + *

+ *

+ * This implementation is designed for an undirected graph. For a valid Eulerian + * circuit to exist, the graph must satisfy two conditions: + *

    + *
  1. All vertices with a non-zero degree must be part of a single connected component.
  2. + *
  3. Every vertex must have an even degree (an even number of edges connected to it).
  4. + *
+ *

+ *

+ * The algorithm runs in O(E + V) time, where E is the number of edges and V is the number of vertices. + * The graph is represented by a Map where keys are vertices and values are a LinkedList of adjacent vertices. + *

+ * + * @see Wikipedia: Hierholzer's algorithm + */ +public final class HierholzerAlgorithm { private final Map> graph; @@ -22,18 +43,13 @@ public boolean hasEulerianCircuit() { return true; } - // FINAL FIX: Loop over values directly for efficiency. for (List neighbors : graph.values()) { if (neighbors.size() % 2 != 0) { return false; } } - if (!isCoherentlyConnected()) { - return false; - } - - return true; + return isCoherentlyConnected(); } public List findEulerianCircuit() { @@ -47,10 +63,9 @@ public List findEulerianCircuit() { } Stack currentPath = new Stack<>(); - List circuit = new LinkedList<>(); + LinkedList circuit = new LinkedList<>(); int startVertex = -1; - // FINAL FIX: Use entrySet for efficiency. for (Map.Entry> entry : tempGraph.entrySet()) { if (!entry.getValue().isEmpty()) { startVertex = entry.getKey(); @@ -75,7 +90,8 @@ public List findEulerianCircuit() { tempGraph.get(nextVertex).remove(Integer.valueOf(currentVertex)); currentPath.push(nextVertex); } else { - circuit.add(0, currentPath.pop()); + circuit.addFirst(currentVertex); + currentPath.pop(); } } @@ -90,7 +106,6 @@ private boolean isCoherentlyConnected() { Set visited = new HashSet<>(); int startNode = -1; - // FINAL FIX: Use entrySet for efficiency. for (Map.Entry> entry : graph.entrySet()) { if (!entry.getValue().isEmpty()) { startNode = entry.getKey(); @@ -104,7 +119,6 @@ private boolean isCoherentlyConnected() { dfs(startNode, visited); - // FINAL FIX: Use entrySet for efficiency. for (Map.Entry> entry : graph.entrySet()) { if (!entry.getValue().isEmpty() && !visited.contains(entry.getKey())) { return false; From ef458a2305be1a49e6b12da071d7b5d8ecb539f3 Mon Sep 17 00:00:00 2001 From: sharan Date: Mon, 13 Oct 2025 06:38:25 +0530 Subject: [PATCH 7/7] docs: Add Hierholzer's Algorithm to DIRECTORY.md --- DIRECTORY.md | 1 + 1 file changed, 1 insertion(+) diff --git a/DIRECTORY.md b/DIRECTORY.md index b311b10fa177..053dbaac5b7e 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -164,6 +164,7 @@ - 📄 [FordFulkerson](src/main/java/com/thealgorithms/datastructures/graphs/FordFulkerson.java) - 📄 [Graphs](src/main/java/com/thealgorithms/datastructures/graphs/Graphs.java) - 📄 [HamiltonianCycle](src/main/java/com/thealgorithms/datastructures/graphs/HamiltonianCycle.java) + - 📄 [HierholzerAlgorithm](src/main/java/com/thealgorithms/graph/HierholzerAlgorithm.java) - 📄 [JohnsonsAlgorithm](src/main/java/com/thealgorithms/datastructures/graphs/JohnsonsAlgorithm.java) - 📄 [KahnsAlgorithm](src/main/java/com/thealgorithms/datastructures/graphs/KahnsAlgorithm.java) - 📄 [Kosaraju](src/main/java/com/thealgorithms/datastructures/graphs/Kosaraju.java)