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) 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..a804f77d7fa6 --- /dev/null +++ b/src/main/java/com/thealgorithms/graph/HierholzerAlgorithm.java @@ -0,0 +1,140 @@ +package com.thealgorithms.graph; + +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; + +/** + * 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; + + public HierholzerAlgorithm(Map> graph) { + this.graph = (graph == null) ? new HashMap<>() : graph; + } + + public boolean hasEulerianCircuit() { + if (graph.isEmpty()) { + return true; + } + + for (List neighbors : graph.values()) { + if (neighbors.size() % 2 != 0) { + return false; + } + } + + return isCoherentlyConnected(); + } + + public List findEulerianCircuit() { + if (!hasEulerianCircuit()) { + return Collections.emptyList(); + } + + Map> tempGraph = new HashMap<>(); + for (Map.Entry> entry : graph.entrySet()) { + tempGraph.put(entry.getKey(), new LinkedList<>(entry.getValue())); + } + + Stack currentPath = new Stack<>(); + LinkedList circuit = new LinkedList<>(); + + int startVertex = -1; + for (Map.Entry> entry : tempGraph.entrySet()) { + if (!entry.getValue().isEmpty()) { + startVertex = entry.getKey(); + break; + } + } + + if (startVertex == -1) { + if (graph.isEmpty()) { + return Collections.emptyList(); + } + return Collections.singletonList(graph.keySet().iterator().next()); + } + + currentPath.push(startVertex); + + while (!currentPath.isEmpty()) { + int currentVertex = currentPath.peek(); + + if (tempGraph.containsKey(currentVertex) && !tempGraph.get(currentVertex).isEmpty()) { + int nextVertex = tempGraph.get(currentVertex).pollFirst(); + tempGraph.get(nextVertex).remove(Integer.valueOf(currentVertex)); + currentPath.push(nextVertex); + } else { + circuit.addFirst(currentVertex); + currentPath.pop(); + } + } + + return circuit; + } + + private boolean isCoherentlyConnected() { + if (graph.isEmpty()) { + return true; + } + + Set visited = new HashSet<>(); + int startNode = -1; + + for (Map.Entry> entry : graph.entrySet()) { + if (!entry.getValue().isEmpty()) { + startNode = entry.getKey(); + break; + } + } + + if (startNode == -1) { + return true; + } + + dfs(startNode, visited); + + for (Map.Entry> entry : graph.entrySet()) { + if (!entry.getValue().isEmpty() && !visited.contains(entry.getKey())) { + return false; + } + } + 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..4dadb206d134 --- /dev/null +++ b/src/test/java/com/thealgorithms/graph/HierholzerAlgorithmTest.java @@ -0,0 +1,60 @@ +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 org.junit.jupiter.api.Test; + +public class HierholzerAlgorithmTest { + + @Test + public void testFindsEulerianCircuitInSimpleTriangleGraph() { + 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); + assertTrue(algorithm.hasEulerianCircuit()); + List circuit = algorithm.findEulerianCircuit(); + assertEquals(4, circuit.size()); + assertEquals(circuit.get(0), circuit.get(circuit.size() - 1)); + } + + @Test + public void testFailsForGraphWithOddDegreeVertices() { + 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); + assertFalse(algorithm.hasEulerianCircuit()); + assertTrue(algorithm.findEulerianCircuit().isEmpty()); + } + + @Test + public void testFailsForDisconnectedGraph() { + 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); + assertFalse(algorithm.hasEulerianCircuit()); + } + + @Test + public void testHandlesEmptyGraph() { + Map> graph = new HashMap<>(); + HierholzerAlgorithm algorithm = new HierholzerAlgorithm(graph); + assertTrue(algorithm.hasEulerianCircuit()); + assertTrue(algorithm.findEulerianCircuit().isEmpty()); + } +}