Skip to content

Commit 568913c

Browse files
committed
fix: Add more test cases to improve code coverage
1 parent d3b0e3b commit 568913c

File tree

2 files changed

+119
-42
lines changed

2 files changed

+119
-42
lines changed

src/main/java/com/thealgorithms/graph/HierholzerAlgorithm.java

Lines changed: 82 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,118 @@
11
package 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
*/
1219
public 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;

src/test/java/com/thealgorithms/graph/HierholzerAlgorithmTest.java

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
package com.thealgorithms.graph;
22

33
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertFalse;
45
import static org.junit.jupiter.api.Assertions.assertTrue;
56

6-
import java.util.*;
7+
import java.util.Arrays;
8+
import java.util.Collections;
9+
import java.util.HashMap;
10+
import java.util.LinkedList;
11+
import java.util.List;
12+
import java.util.Map;
713
import org.junit.jupiter.api.Test;
814

915
public class HierholzerAlgorithmTest {
@@ -31,16 +37,42 @@ public void testFindsEulerianCircuitInSimpleTriangleGraph() {
3137
}
3238

3339
@Test
34-
public void testHandlesGraphWithNoEulerianCircuit() {
35-
// Create a graph where a vertex has an odd degree
40+
public void testFailsForGraphWithOddDegreeVertices() {
41+
// Create a graph where vertices 0 and 1 have an odd degree (1)
3642
Map<Integer, LinkedList<Integer>> graph = new HashMap<>();
3743
graph.put(0, new LinkedList<>(Collections.singletonList(1)));
3844
graph.put(1, new LinkedList<>(Collections.singletonList(0)));
39-
graph.put(2, new LinkedList<>(Collections.emptyList())); // Vertex 2 is isolated
4045

4146
HierholzerAlgorithm algorithm = new HierholzerAlgorithm(graph);
4247

4348
// The algorithm should correctly identify that no circuit exists
44-
assertEquals(false, algorithm.hasEulerianCircuit());
49+
assertFalse(algorithm.hasEulerianCircuit());
50+
// The find method should return an empty list
51+
assertTrue(algorithm.findEulerianCircuit().isEmpty());
52+
}
53+
54+
@Test
55+
public void testFailsForDisconnectedGraph() {
56+
// Create a graph with two separate triangles (0-1-2 and 3-4-5)
57+
Map<Integer, LinkedList<Integer>> graph = new HashMap<>();
58+
graph.put(0, new LinkedList<>(Arrays.asList(1, 2)));
59+
graph.put(1, new LinkedList<>(Arrays.asList(0, 2)));
60+
graph.put(2, new LinkedList<>(Arrays.asList(0, 1)));
61+
graph.put(3, new LinkedList<>(Arrays.asList(4, 5)));
62+
graph.put(4, new LinkedList<>(Arrays.asList(3, 5)));
63+
graph.put(5, new LinkedList<>(Arrays.asList(3, 4)));
64+
65+
HierholzerAlgorithm algorithm = new HierholzerAlgorithm(graph);
66+
67+
// All degrees are even, but the graph is not connected, so no circuit exists
68+
assertFalse(algorithm.hasEulerianCircuit());
69+
}
70+
71+
@Test
72+
public void testHandlesEmptyGraph() {
73+
Map<Integer, LinkedList<Integer>> graph = new HashMap<>();
74+
HierholzerAlgorithm algorithm = new HierholzerAlgorithm(graph);
75+
assertTrue(algorithm.hasEulerianCircuit());
76+
assertTrue(algorithm.findEulerianCircuit().isEmpty());
4577
}
4678
}

0 commit comments

Comments
 (0)