Skip to content

Commit d3b0e3b

Browse files
committed
feat: Add Hierholzer's Algorithm for Eulerian Circuits
1 parent c691b31 commit d3b0e3b

File tree

2 files changed

+171
-0
lines changed

2 files changed

+171
-0
lines changed
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package com.thealgorithms.graph;
2+
3+
import java.util.*;
4+
5+
/**
6+
* 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.
9+
*
10+
* Wikipedia: https://en.wikipedia.org/wiki/Eulerian_path#Hierholzer's_algorithm
11+
*/
12+
public class HierholzerAlgorithm {
13+
14+
private final Map<Integer, LinkedList<Integer>> graph;
15+
16+
/**
17+
* Sets up the algorithm with the graph we want to solve.
18+
* @param graph The graph represented as an adjacency list.
19+
*/
20+
public HierholzerAlgorithm(Map<Integer, LinkedList<Integer>> graph) {
21+
this.graph = (graph == null) ? new HashMap<>() : graph;
22+
}
23+
24+
/**
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.
28+
*/
29+
public boolean hasEulerianCircuit() {
30+
if (graph.isEmpty()) {
31+
return true; // An empty puzzle is trivially solved.
32+
}
33+
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.
36+
for (int vertex : graph.keySet()) {
37+
if (graph.get(vertex).size() % 2 != 0) {
38+
return false; // Found a point with an odd number of lines.
39+
}
40+
}
41+
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();
45+
}
46+
47+
/**
48+
* This is the main event—finding the actual path.
49+
* @return A list of points (vertices) that make up the complete circuit.
50+
*/
51+
public List<Integer> findEulerianCircuit() {
52+
if (!hasEulerianCircuit()) {
53+
// If the puzzle can't be solved, return an empty path.
54+
return Collections.emptyList();
55+
}
56+
57+
// We'll work on a copy of the graph so we don't destroy the original.
58+
Map<Integer, LinkedList<Integer>> tempGraph = new HashMap<>();
59+
for (Map.Entry<Integer, LinkedList<Integer>> entry : graph.entrySet()) {
60+
tempGraph.put(entry.getKey(), new LinkedList<>(entry.getValue()));
61+
}
62+
63+
// 'currentPath' is our breadcrumb trail as we explore.
64+
Stack<Integer> currentPath = new Stack<>();
65+
// 'circuit' is where we'll lay out the final, complete path.
66+
List<Integer> circuit = new LinkedList<>();
67+
68+
// Find any point to start from.
69+
int startVertex = graph.keySet().stream().findFirst().orElse(-1);
70+
if (startVertex == -1) return Collections.emptyList();
71+
72+
currentPath.push(startVertex);
73+
74+
while (!currentPath.isEmpty()) {
75+
int currentVertex = currentPath.peek();
76+
77+
// If there's an unexplored hallway from our current location...
78+
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.
82+
tempGraph.get(nextVertex).remove(Integer.valueOf(currentVertex));
83+
// Add the new location to our breadcrumb trail.
84+
currentPath.push(nextVertex);
85+
} 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.
88+
circuit.add(0, currentPath.pop());
89+
}
90+
}
91+
92+
return circuit;
93+
}
94+
95+
/**
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.
98+
*/
99+
private boolean isCoherentlyConnected() {
100+
if (graph.isEmpty()) return true;
101+
Set<Integer> visited = new HashSet<>();
102+
int startNode = graph.keySet().stream().findFirst().orElse(-1);
103+
if (startNode == -1) return true;
104+
105+
dfs(startNode, visited);
106+
107+
for (int vertex : graph.keySet()) {
108+
if (!graph.get(vertex).isEmpty() && !visited.contains(vertex)) {
109+
return false; // Found a part of the puzzle we couldn't reach.
110+
}
111+
}
112+
return true;
113+
}
114+
115+
private void dfs(int u, Set<Integer> visited) {
116+
visited.add(u);
117+
if (graph.containsKey(u)) {
118+
for (int v : graph.get(u)) {
119+
if (!visited.contains(v)) {
120+
dfs(v, visited);
121+
}
122+
}
123+
}
124+
}
125+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.thealgorithms.graph;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertTrue;
5+
6+
import java.util.*;
7+
import org.junit.jupiter.api.Test;
8+
9+
public class HierholzerAlgorithmTest {
10+
11+
@Test
12+
public void testFindsEulerianCircuitInSimpleTriangleGraph() {
13+
// Create a simple triangle graph where a circuit definitely exists: 0-1, 1-2, 2-0
14+
Map<Integer, LinkedList<Integer>> graph = new HashMap<>();
15+
graph.put(0, new LinkedList<>(Arrays.asList(1, 2)));
16+
graph.put(1, new LinkedList<>(Arrays.asList(0, 2)));
17+
graph.put(2, new LinkedList<>(Arrays.asList(0, 1)));
18+
19+
HierholzerAlgorithm algorithm = new HierholzerAlgorithm(graph);
20+
21+
// Verify that the algorithm agrees a circuit exists
22+
assertTrue(algorithm.hasEulerianCircuit());
23+
24+
List<Integer> circuit = algorithm.findEulerianCircuit();
25+
26+
// A valid circuit for a triangle has 3 edges, so the path will have 4 vertices (e.g., 0 -> 1 -> 2 -> 0)
27+
assertEquals(4, circuit.size());
28+
29+
// The path must start and end at the same vertex
30+
assertEquals(circuit.get(0), circuit.get(circuit.size() - 1));
31+
}
32+
33+
@Test
34+
public void testHandlesGraphWithNoEulerianCircuit() {
35+
// Create a graph where a vertex has an odd degree
36+
Map<Integer, LinkedList<Integer>> graph = new HashMap<>();
37+
graph.put(0, new LinkedList<>(Collections.singletonList(1)));
38+
graph.put(1, new LinkedList<>(Collections.singletonList(0)));
39+
graph.put(2, new LinkedList<>(Collections.emptyList())); // Vertex 2 is isolated
40+
41+
HierholzerAlgorithm algorithm = new HierholzerAlgorithm(graph);
42+
43+
// The algorithm should correctly identify that no circuit exists
44+
assertEquals(false, algorithm.hasEulerianCircuit());
45+
}
46+
}

0 commit comments

Comments
 (0)