Skip to content

Commit 6b7d201

Browse files
Hierholzer path algorithm (#6822)
* Added HierholzerEulerianPath algorithm * Added Hierholzer Algorith to find Eulerian Path * Added Hierholzer Algorith to find Eulerian Path * Added Hierholzer Algorith to find Eulerian Path * Added Hierholzer Algorith to find Eulerian Path * Added Hierholzer Algorith to find Eulerian Path * Added Hierholzer Algorith to find Eulerian Path * Added Hierholzer Algorith to find Eulerian Path --------- Co-authored-by: crashmovies <[email protected]>
1 parent 4858ec9 commit 6b7d201

File tree

2 files changed

+472
-0
lines changed

2 files changed

+472
-0
lines changed
Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
1+
package com.thealgorithms.graph;
2+
3+
import java.util.ArrayDeque;
4+
import java.util.ArrayList;
5+
import java.util.Collections;
6+
import java.util.Deque;
7+
import java.util.List;
8+
9+
/**
10+
* Implementation of Hierholzer's Algorithm for finding an Eulerian Path or Circuit
11+
* in a directed graph.
12+
*
13+
* <p>
14+
* An <b>Eulerian Circuit</b> is a path that starts and ends at the same vertex
15+
* and visits every edge exactly once.
16+
* </p>
17+
*
18+
* <p>
19+
* An <b>Eulerian Path</b> visits every edge exactly once but may start and end
20+
* at different vertices.
21+
* </p>
22+
*
23+
* <p>
24+
* <b>Algorithm Summary:</b><br>
25+
* 1. Compute indegree and outdegree for all vertices.<br>
26+
* 2. Check if the graph satisfies Eulerian path or circuit conditions.<br>
27+
* 3. Verify that all vertices with non-zero degree are weakly connected (undirected connectivity).<br>
28+
* 4. Use Hierholzer’s algorithm to build the path by exploring unused edges iteratively.
29+
* </p>
30+
*
31+
* <p>
32+
* <b>Time Complexity:</b> O(E + V).<br>
33+
* <b>Space Complexity:</b> O(V + E).
34+
* </p>
35+
*
36+
* @author <a href="https://en.wikipedia.org/wiki/Eulerian_path#Hierholzer's_algorithm">Wikipedia: Hierholzer algorithm</a>
37+
*/
38+
public class HierholzerEulerianPath {
39+
40+
/**
41+
* Simple directed graph represented by adjacency lists.
42+
*/
43+
public static class Graph {
44+
private final List<List<Integer>> adjacencyList;
45+
46+
/**
47+
* Constructs a graph with a given number of vertices.
48+
*
49+
* @param numNodes number of vertices
50+
*/
51+
public Graph(int numNodes) {
52+
adjacencyList = new ArrayList<>();
53+
for (int i = 0; i < numNodes; i++) {
54+
adjacencyList.add(new ArrayList<>());
55+
}
56+
}
57+
58+
/**
59+
* Adds a directed edge from vertex {@code from} to vertex {@code to}.
60+
*
61+
* @param from source vertex
62+
* @param to destination vertex
63+
*/
64+
public void addEdge(int from, int to) {
65+
adjacencyList.get(from).add(to);
66+
}
67+
68+
/**
69+
* Returns a list of outgoing edges from the given vertex.
70+
*
71+
* @param node vertex index
72+
* @return list of destination vertices
73+
*/
74+
public List<Integer> getEdges(int node) {
75+
return adjacencyList.get(node);
76+
}
77+
78+
/**
79+
* Returns the number of vertices in the graph.
80+
*
81+
* @return number of vertices
82+
*/
83+
public int getNumNodes() {
84+
return adjacencyList.size();
85+
}
86+
}
87+
88+
private final Graph graph;
89+
90+
/**
91+
* Creates a Hierholzer solver for the given graph.
92+
*
93+
* @param graph directed graph
94+
*/
95+
public HierholzerEulerianPath(Graph graph) {
96+
this.graph = graph;
97+
}
98+
99+
/**
100+
* Finds an Eulerian Path or Circuit using Hierholzer’s Algorithm.
101+
*
102+
* @return list of vertices representing the Eulerian Path/Circuit,
103+
* or an empty list if none exists
104+
*/
105+
public List<Integer> findEulerianPath() {
106+
int n = graph.getNumNodes();
107+
108+
// empty graph -> no path
109+
if (n == 0) {
110+
return new ArrayList<>();
111+
}
112+
113+
int[] inDegree = new int[n];
114+
int[] outDegree = new int[n];
115+
int edgeCount = computeDegrees(inDegree, outDegree);
116+
117+
// no edges -> single vertex response requested by tests: [0]
118+
if (edgeCount == 0) {
119+
return Collections.singletonList(0);
120+
}
121+
122+
int startNode = determineStartNode(inDegree, outDegree);
123+
if (startNode == -1) {
124+
return new ArrayList<>();
125+
}
126+
127+
if (!allNonZeroDegreeVerticesWeaklyConnected(startNode, n, outDegree, inDegree)) {
128+
return new ArrayList<>();
129+
}
130+
131+
List<Integer> path = buildHierholzerPath(startNode, n);
132+
if (path.size() != edgeCount + 1) {
133+
return new ArrayList<>();
134+
}
135+
136+
return rotateEulerianCircuitIfNeeded(path, outDegree, inDegree);
137+
}
138+
139+
private int computeDegrees(int[] inDegree, int[] outDegree) {
140+
int edgeCount = 0;
141+
for (int u = 0; u < graph.getNumNodes(); u++) {
142+
for (int v : graph.getEdges(u)) {
143+
outDegree[u]++;
144+
inDegree[v]++;
145+
edgeCount++;
146+
}
147+
}
148+
return edgeCount;
149+
}
150+
151+
private int determineStartNode(int[] inDegree, int[] outDegree) {
152+
int n = graph.getNumNodes();
153+
int startNode = -1;
154+
int startCount = 0;
155+
int endCount = 0;
156+
157+
for (int i = 0; i < n; i++) {
158+
int diff = outDegree[i] - inDegree[i];
159+
if (diff == 1) {
160+
startNode = i;
161+
startCount++;
162+
} else if (diff == -1) {
163+
endCount++;
164+
} else if (Math.abs(diff) > 1) {
165+
return -1;
166+
}
167+
}
168+
169+
if (!((startCount == 1 && endCount == 1) || (startCount == 0 && endCount == 0))) {
170+
return -1;
171+
}
172+
173+
if (startNode == -1) {
174+
for (int i = 0; i < n; i++) {
175+
if (outDegree[i] > 0) {
176+
startNode = i;
177+
break;
178+
}
179+
}
180+
}
181+
return startNode;
182+
}
183+
184+
private List<Integer> buildHierholzerPath(int startNode, int n) {
185+
List<Deque<Integer>> tempAdj = new ArrayList<>();
186+
for (int i = 0; i < n; i++) {
187+
tempAdj.add(new ArrayDeque<>(graph.getEdges(i)));
188+
}
189+
190+
Deque<Integer> stack = new ArrayDeque<>();
191+
List<Integer> path = new ArrayList<>();
192+
stack.push(startNode);
193+
194+
while (!stack.isEmpty()) {
195+
int u = stack.peek();
196+
if (!tempAdj.get(u).isEmpty()) {
197+
stack.push(tempAdj.get(u).pollFirst());
198+
} else {
199+
path.add(stack.pop());
200+
}
201+
}
202+
203+
Collections.reverse(path);
204+
return path;
205+
}
206+
207+
private List<Integer> rotateEulerianCircuitIfNeeded(List<Integer> path, int[] outDegree, int[] inDegree) {
208+
int startCount = 0;
209+
int endCount = 0;
210+
for (int i = 0; i < outDegree.length; i++) {
211+
int diff = outDegree[i] - inDegree[i];
212+
if (diff == 1) {
213+
startCount++;
214+
} else if (diff == -1) {
215+
endCount++;
216+
}
217+
}
218+
219+
if (startCount == 0 && endCount == 0 && !path.isEmpty()) {
220+
int preferredStart = -1;
221+
for (int i = 0; i < outDegree.length; i++) {
222+
if (outDegree[i] > 0) {
223+
preferredStart = i;
224+
break;
225+
}
226+
}
227+
228+
if (preferredStart != -1 && path.get(0) != preferredStart) {
229+
int idx = 0;
230+
for (Integer node : path) { // replaced indexed loop
231+
if (node == preferredStart) {
232+
break;
233+
}
234+
idx++;
235+
}
236+
237+
if (idx > 0) {
238+
List<Integer> rotated = new ArrayList<>();
239+
int currentIndex = 0;
240+
for (Integer node : path) { // replaced indexed loop
241+
if (currentIndex >= idx) {
242+
rotated.add(node);
243+
}
244+
currentIndex++;
245+
}
246+
currentIndex = 0;
247+
for (Integer node : path) { // replaced indexed loop
248+
if (currentIndex < idx) {
249+
rotated.add(node);
250+
}
251+
currentIndex++;
252+
}
253+
path = rotated;
254+
}
255+
}
256+
}
257+
return path;
258+
}
259+
260+
/**
261+
* Checks weak connectivity (undirected) among vertices that have non-zero degree.
262+
*
263+
* @param startNode node to start DFS from (must be a vertex with non-zero degree)
264+
* @param n number of vertices
265+
* @param outDegree out-degree array
266+
* @param inDegree in-degree array
267+
* @return true if all vertices having non-zero degree belong to a single weak component
268+
*/
269+
private boolean allNonZeroDegreeVerticesWeaklyConnected(int startNode, int n, int[] outDegree, int[] inDegree) {
270+
boolean[] visited = new boolean[n];
271+
Deque<Integer> stack = new ArrayDeque<>();
272+
stack.push(startNode);
273+
visited[startNode] = true;
274+
275+
while (!stack.isEmpty()) {
276+
int u = stack.pop();
277+
for (int v : graph.getEdges(u)) {
278+
if (!visited[v]) {
279+
visited[v] = true;
280+
stack.push(v);
281+
}
282+
}
283+
for (int x = 0; x < n; x++) {
284+
if (!visited[x]) {
285+
for (int y : graph.getEdges(x)) {
286+
if (y == u) {
287+
visited[x] = true;
288+
stack.push(x);
289+
break;
290+
}
291+
}
292+
}
293+
}
294+
}
295+
296+
for (int i = 0; i < n; i++) {
297+
if (outDegree[i] + inDegree[i] > 0 && !visited[i]) {
298+
return false;
299+
}
300+
}
301+
return true;
302+
}
303+
}

0 commit comments

Comments
 (0)