All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jgrapht.alg.cycle.HierholzerEulerianCycle Maven / Gradle / Ivy

The newest version!
/*
 * (C) Copyright 2016-2023, by Dimitrios Michail and Contributors.
 *
 * JGraphT : a free Java graph-theory library
 *
 * See the CONTRIBUTORS.md file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0, or the
 * GNU Lesser General Public License v2.1 or later
 * which is available at
 * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later
 */
package org.jgrapht.alg.cycle;

import org.jgrapht.*;
import org.jgrapht.alg.connectivity.*;
import org.jgrapht.alg.interfaces.*;
import org.jgrapht.alg.util.*;
import org.jgrapht.graph.*;
import org.jgrapht.util.*;

import java.util.*;

/**
 * An implementation of Hierholzer's algorithm for finding an Eulerian cycle in Eulerian graphs. The
 * algorithm works with directed and undirected graphs which may contain loops and/or multiple
 * (parallel) edges. The running time is linear, i.e. $O(|E|)$ where $|E|$ is the cardinality of the
 * edge set of the graph.
 * 
 * 

* See the Wikipedia article for details * and references about Eulerian cycles and a short description of Hierholzer's algorithm for the * construction of an Eulerian cycle. The original presentation of the algorithm dates back to 1873 * and the following paper: Carl Hierholzer: Über die Möglichkeit, einen Linienzug ohne * Wiederholung und ohne Unterbrechung zu umfahren. Mathematische Annalen 6(1), 30–32, 1873. * * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ public class HierholzerEulerianCycle implements EulerianCycleAlgorithm { /* * The input graph. */ protected Graph g; /* * Whether the graph is directed or not. */ protected boolean isDirected; /* * Non-zero degree vertices list head. */ protected VertexNode verticesHead; /* * Result edge list head. */ protected EdgeNode eulerianHead; /* * Result first vertex in the tour. */ protected V startVertex; /** * Test whether a graph is Eulerian. An * Eulerian graph is a graph * containing an Eulerian cycle. * * @param graph the input graph * @return true if the graph is Eulerian, false otherwise */ public boolean isEulerian(Graph graph) { GraphTests.requireDirectedOrUndirected(graph); if (graph.vertexSet().isEmpty()) { // null-graph return false return false; } else if (graph.edgeSet().isEmpty()) { // empty-graph with vertices return true; } else if (graph.getType().isUndirected()) { // check odd degrees for (V v : graph.vertexSet()) { if (graph.degreeOf(v) % 2 == 1) { return false; } } // check that at most one connected component contains edges boolean foundComponentWithEdges = false; for (Set component : new ConnectivityInspector<>(graph).connectedSets()) { for (V v : component) { if (graph.degreeOf(v) > 0) { if (foundComponentWithEdges) { return false; } foundComponentWithEdges = true; break; } } } return true; } else { // check same in and out degrees for (V v : graph.vertexSet()) { if (graph.inDegreeOf(v) != graph.outDegreeOf(v)) { return false; } } // check that at most one strongly connected component contains // edges boolean foundComponentWithEdges = false; for (Set component : new KosarajuStrongConnectivityInspector<>(graph) .stronglyConnectedSets()) { for (V v : component) { if (graph.inDegreeOf(v) > 0 || graph.outDegreeOf(v) > 0) { if (foundComponentWithEdges) { return false; } foundComponentWithEdges = true; break; } } } return true; } } /** * Compute an Eulerian cycle of a graph. * * @param g the input graph * @return an Eulerian cycle * @throws IllegalArgumentException in case the graph is not Eulerian */ public GraphPath getEulerianCycle(Graph g) { if (!isEulerian(g)) { throw new IllegalArgumentException("Graph is not Eulerian"); } else if (g.vertexSet().isEmpty()) { throw new IllegalArgumentException("Null graph not permitted"); } else if (GraphTests.isEmpty(g)) { return GraphWalk.emptyWalk(g); } /* * Create doubly-linked lists */ initialize(g); /* * Main loop */ while (verticesHead != null) { /* * Record where to insert next partial cycle. */ EdgeNode whereToInsert = verticesHead.insertLocation; /* * Find partial cycle, while removing used edges. */ Pair partialCycle = computePartialCycle(); /* * Iterate over partial cycle to remove vertices with zero degrees and compute new * insert locations for vertices with non-zero degrees. It is important to move vertices * with new insert locations to the front of the vertex list, in order to make sure that * we always start partial cycles from already visited vertices. */ updateGraphAndInsertLocations(partialCycle, verticesHead); /* * Insert partial cycle into Eulerian cycle */ if (whereToInsert == null) { eulerianHead = partialCycle.getFirst(); } else { partialCycle.getSecond().next = whereToInsert.next; whereToInsert.next = partialCycle.getFirst(); } } // build final result GraphWalk walk = buildWalk(); // cleanup cleanup(); return walk; } /** * Index the graph and create a double-linked list representation suitable for vertex and edge * removals in constant time. Ignore any vertices with zero degrees. * * @param g the graph to index */ protected void initialize(Graph g) { this.g = g; this.isDirected = g.getType().isDirected(); this.verticesHead = null; this.eulerianHead = null; this.startVertex = null; Map vertices = new HashMap<>(); for (V v : g.vertexSet()) { if (g.outDegreeOf(v) > 0) { VertexNode n = new VertexNode(null, v, verticesHead); if (verticesHead != null) { verticesHead.prev = n; } verticesHead = n; vertices.put(v, n); } } for (E e : g.edgeSet()) { VertexNode sNode = vertices.get(g.getEdgeSource(e)); VertexNode tNode = vertices.get(g.getEdgeTarget(e)); addEdge(sNode, tNode, e); } } /** * Release any memory held. */ protected void cleanup() { this.g = null; this.verticesHead = null; this.eulerianHead = null; this.startVertex = null; } /** * Computes a partial cycle assuming that all vertices have an even degree. The partial cycle * always begin from the first graph vertex in the vertex list. * * @return the partial's cycle head and tail nodes as a pair */ protected Pair computePartialCycle() { if (startVertex == null) { // record global start vertex startVertex = verticesHead.v; } EdgeNode partialHead = null; EdgeNode partialTail = null; VertexNode v = verticesHead; do { EdgeNode e = v.adjEdgesHead; v = getOppositeVertex(v, e); unlink(e); if (partialTail == null) { partialTail = e; partialHead = partialTail; } else { partialTail.next = e; partialTail = partialTail.next; } } while (!v.equals(verticesHead)); return Pair.of(partialHead, partialTail); } /** * Iterate over the partial cycle to remove vertices with zero degrees and compute new insert * locations for vertices with non-zero degrees. It is important to move vertices with new * insert locations to the front of the vertex list, in order to make sure that we always start * partial cycles from already visited vertices. * * @param partialCycle the partial cycle * @param partialCycleSourceVertex the source vertex of the first edge in the partial cycle */ protected void updateGraphAndInsertLocations( Pair partialCycle, VertexNode partialCycleSourceVertex) { EdgeNode e = partialCycle.getFirst(); assert e != null : "Graph is not Eulerian"; VertexNode v = getOppositeVertex(partialCycleSourceVertex, e); while (true) { if (v.adjEdgesHead != null) { v.insertLocation = e; moveToFront(v); } else { unlink(v); } e = e.next; if (e == null) { break; } v = getOppositeVertex(v, e); } } /** * Build final walk * * @return the final walk */ protected GraphWalk buildWalk() { double totalWeight = 0d; List result = new ArrayList<>(); EdgeNode it = eulerianHead; while (it != null) { result.add(it.e); totalWeight += g.getEdgeWeight(it.e); it = it.next; } return new GraphWalk<>(g, startVertex, startVertex, result, totalWeight); } /** * Add an edge to the index. * * @param sNode source vertex * @param tNode target vertex * @param e original (wrapped) edge */ protected void addEdge(VertexNode sNode, VertexNode tNode, E e) { EdgeNode sHead = sNode.adjEdgesHead; if (sHead == null) { sHead = new EdgeNode(sNode, tNode, null, e, null, null); } else { EdgeNode n = new EdgeNode(sNode, tNode, null, e, null, sHead); sHead.prev = n; sHead = n; } sNode.adjEdgesHead = sHead; // if undirected and not a self-loop, add edge to target if (!isDirected && !sNode.equals(tNode)) { EdgeNode tHead = tNode.adjEdgesHead; if (tHead == null) { tHead = new EdgeNode(tNode, sNode, null, e, sHead, null); } else { EdgeNode n = new EdgeNode(tNode, sNode, null, e, sHead, tHead); tHead.prev = n; tHead = n; } sHead.reverse = tHead; tNode.adjEdgesHead = tHead; } } /** * Unlink a vertex from the vertex list. * * @param vNode vertex to unlink */ protected void unlink(VertexNode vNode) { if (verticesHead == null) { return; } else if (!verticesHead.equals(vNode) && vNode.prev == null && vNode.next == null) { // does not belong to list return; } else if (vNode.prev != null) { vNode.prev.next = vNode.next; if (vNode.next != null) { vNode.next.prev = vNode.prev; } } else { verticesHead = vNode.next; if (verticesHead != null) { verticesHead.prev = null; } } vNode.next = null; vNode.prev = null; } /** * Move a vertex as first in the vertex list. * * @param vNode vertex to move to front */ protected void moveToFront(VertexNode vNode) { if (vNode.prev != null) { vNode.prev.next = vNode.next; if (vNode.next != null) { vNode.next.prev = vNode.prev; } verticesHead.prev = vNode; vNode.next = verticesHead; vNode.prev = null; verticesHead = vNode; } } /** * Unlink an edge from the adjacency lists of its end-points. * * @param eNode edge to unlink */ protected void unlink(EdgeNode eNode) { VertexNode vNode = eNode.sourceNode; if (eNode.prev != null) { eNode.prev.next = eNode.next; if (eNode.next != null) { eNode.next.prev = eNode.prev; } } else { if (eNode.next != null) { eNode.next.prev = null; } vNode.adjEdgesHead = eNode.next; } // remove reverse if (!isDirected && eNode.reverse != null) { EdgeNode revNode = eNode.reverse; VertexNode uNode = revNode.sourceNode; if (revNode.prev != null) { revNode.prev.next = revNode.next; if (revNode.next != null) { revNode.next.prev = revNode.prev; } } else { if (revNode.next != null) { revNode.next.prev = null; } uNode.adjEdgesHead = revNode.next; } } eNode.next = null; eNode.prev = null; eNode.reverse = null; } /** * Compute the opposite end-point of an end-point of an edge. * * @param v vertex that is part of edge * @param e edge used to find opposite vertex * @return opposite vertex in edge */ protected VertexNode getOppositeVertex(VertexNode v, EdgeNode e) { return v.equals(e.sourceNode) ? e.targetNode : e.sourceNode; } /* * A list node for the vertices */ protected class VertexNode { // actual vertex public V v; // list public VertexNode prev; public VertexNode next; // insert location in global Eulerian list public EdgeNode insertLocation; // adjacent edges public EdgeNode adjEdgesHead; /** * Create VertexNode * * @param prev previous vertex * @param v original (wrapped) vertex * @param next next vertex */ public VertexNode(VertexNode prev, V v, VertexNode next) { this.prev = prev; this.v = v; this.next = next; this.adjEdgesHead = null; this.insertLocation = null; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((v == null) ? 0 : v.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; VertexNode other = TypeUtil.uncheckedCast(obj); return Objects.equals(this.v, other.v); } @Override public String toString() { return v.toString(); } } /* * A list node for the edges */ protected class EdgeNode { // the edge public E e; // list public EdgeNode next; public EdgeNode prev; // reverse if undirected and not a self-loop public EdgeNode reverse; // source and target public VertexNode sourceNode; public VertexNode targetNode; /** * Create EdgeNode * * @param sourceNode source vertex * @param targetNode target vertex * @param prev previous edge * @param e wrapped (original) edge * @param reverse reverse edge * @param next next edge */ public EdgeNode( VertexNode sourceNode, VertexNode targetNode, EdgeNode prev, E e, EdgeNode reverse, EdgeNode next) { this.sourceNode = sourceNode; this.targetNode = targetNode; this.prev = prev; this.e = e; this.reverse = reverse; this.next = next; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((e == null) ? 0 : e.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; EdgeNode other = TypeUtil.uncheckedCast(obj); return Objects.equals(this.e, other.e); } @Override public String toString() { return e.toString(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy