package com.thealgorithms.graph;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.List;
/**
* Implementation of Hierholzer's Algorithm for finding an Eulerian Path or Circuit
* in a directed graph.
*
*
* An Eulerian Circuit is a path that starts and ends at the same vertex
* and visits every edge exactly once.
*
*
*
* An Eulerian Path visits every edge exactly once but may start and end
* at different vertices.
*
*
*
* Algorithm Summary:
* 1. Compute indegree and outdegree for all vertices.
* 2. Check if the graph satisfies Eulerian path or circuit conditions.
* 3. Verify that all vertices with non-zero degree are weakly connected (undirected connectivity).
* 4. Use Hierholzer’s algorithm to build the path by exploring unused edges iteratively.
*
*
*
* Time Complexity: O(E + V).
* Space Complexity: O(V + E).
*
*
* @author Wikipedia: Hierholzer algorithm
*/
public class HierholzerEulerianPath {
/**
* Simple directed graph represented by adjacency lists.
*/
public static class Graph {
private final List> adjacencyList;
/**
* Constructs a graph with a given number of vertices.
*
* @param numNodes number of vertices
*/
public Graph(int numNodes) {
adjacencyList = new ArrayList<>();
for (int i = 0; i < numNodes; i++) {
adjacencyList.add(new ArrayList<>());
}
}
/**
* Adds a directed edge from vertex {@code from} to vertex {@code to}.
*
* @param from source vertex
* @param to destination vertex
*/
public void addEdge(int from, int to) {
adjacencyList.get(from).add(to);
}
/**
* Returns a list of outgoing edges from the given vertex.
*
* @param node vertex index
* @return list of destination vertices
*/
public List getEdges(int node) {
return adjacencyList.get(node);
}
/**
* Returns the number of vertices in the graph.
*
* @return number of vertices
*/
public int getNumNodes() {
return adjacencyList.size();
}
}
private final Graph graph;
/**
* Creates a Hierholzer solver for the given graph.
*
* @param graph directed graph
*/
public HierholzerEulerianPath(Graph graph) {
this.graph = graph;
}
/**
* Finds an Eulerian Path or Circuit using Hierholzer’s Algorithm.
*
* @return list of vertices representing the Eulerian Path/Circuit,
* or an empty list if none exists
*/
public List findEulerianPath() {
int n = graph.getNumNodes();
// empty graph -> no path
if (n == 0) {
return new ArrayList<>();
}
int[] inDegree = new int[n];
int[] outDegree = new int[n];
int edgeCount = computeDegrees(inDegree, outDegree);
// no edges -> single vertex response requested by tests: [0]
if (edgeCount == 0) {
return Collections.singletonList(0);
}
int startNode = determineStartNode(inDegree, outDegree);
if (startNode == -1) {
return new ArrayList<>();
}
if (!allNonZeroDegreeVerticesWeaklyConnected(startNode, n, outDegree, inDegree)) {
return new ArrayList<>();
}
List path = buildHierholzerPath(startNode, n);
if (path.size() != edgeCount + 1) {
return new ArrayList<>();
}
return rotateEulerianCircuitIfNeeded(path, outDegree, inDegree);
}
private int computeDegrees(int[] inDegree, int[] outDegree) {
int edgeCount = 0;
for (int u = 0; u < graph.getNumNodes(); u++) {
for (int v : graph.getEdges(u)) {
outDegree[u]++;
inDegree[v]++;
edgeCount++;
}
}
return edgeCount;
}
private int determineStartNode(int[] inDegree, int[] outDegree) {
int n = graph.getNumNodes();
int startNode = -1;
int startCount = 0;
int endCount = 0;
for (int i = 0; i < n; i++) {
int diff = outDegree[i] - inDegree[i];
if (diff == 1) {
startNode = i;
startCount++;
} else if (diff == -1) {
endCount++;
} else if (Math.abs(diff) > 1) {
return -1;
}
}
if (!((startCount == 1 && endCount == 1) || (startCount == 0 && endCount == 0))) {
return -1;
}
if (startNode == -1) {
for (int i = 0; i < n; i++) {
if (outDegree[i] > 0) {
startNode = i;
break;
}
}
}
return startNode;
}
private List buildHierholzerPath(int startNode, int n) {
List> tempAdj = new ArrayList<>();
for (int i = 0; i < n; i++) {
tempAdj.add(new ArrayDeque<>(graph.getEdges(i)));
}
Deque stack = new ArrayDeque<>();
List path = new ArrayList<>();
stack.push(startNode);
while (!stack.isEmpty()) {
int u = stack.peek();
if (!tempAdj.get(u).isEmpty()) {
stack.push(tempAdj.get(u).pollFirst());
} else {
path.add(stack.pop());
}
}
Collections.reverse(path);
return path;
}
private List rotateEulerianCircuitIfNeeded(List path, int[] outDegree, int[] inDegree) {
int startCount = 0;
int endCount = 0;
for (int i = 0; i < outDegree.length; i++) {
int diff = outDegree[i] - inDegree[i];
if (diff == 1) {
startCount++;
} else if (diff == -1) {
endCount++;
}
}
if (startCount == 0 && endCount == 0 && !path.isEmpty()) {
int preferredStart = -1;
for (int i = 0; i < outDegree.length; i++) {
if (outDegree[i] > 0) {
preferredStart = i;
break;
}
}
if (preferredStart != -1 && path.get(0) != preferredStart) {
int idx = 0;
for (Integer node : path) { // replaced indexed loop
if (node == preferredStart) {
break;
}
idx++;
}
if (idx > 0) {
List rotated = new ArrayList<>();
int currentIndex = 0;
for (Integer node : path) { // replaced indexed loop
if (currentIndex >= idx) {
rotated.add(node);
}
currentIndex++;
}
currentIndex = 0;
for (Integer node : path) { // replaced indexed loop
if (currentIndex < idx) {
rotated.add(node);
}
currentIndex++;
}
path = rotated;
}
}
}
return path;
}
/**
* Checks weak connectivity (undirected) among vertices that have non-zero degree.
*
* @param startNode node to start DFS from (must be a vertex with non-zero degree)
* @param n number of vertices
* @param outDegree out-degree array
* @param inDegree in-degree array
* @return true if all vertices having non-zero degree belong to a single weak component
*/
private boolean allNonZeroDegreeVerticesWeaklyConnected(int startNode, int n, int[] outDegree, int[] inDegree) {
boolean[] visited = new boolean[n];
Deque stack = new ArrayDeque<>();
stack.push(startNode);
visited[startNode] = true;
while (!stack.isEmpty()) {
int u = stack.pop();
for (int v : graph.getEdges(u)) {
if (!visited[v]) {
visited[v] = true;
stack.push(v);
}
}
for (int x = 0; x < n; x++) {
if (!visited[x]) {
for (int y : graph.getEdges(x)) {
if (y == u) {
visited[x] = true;
stack.push(x);
break;
}
}
}
}
}
for (int i = 0; i < n; i++) {
if (outDegree[i] + inDegree[i] > 0 && !visited[i]) {
return false;
}
}
return true;
}
}