org.jgrapht.alg.cycle.AbstractFundamentalCycleBasis Maven / Gradle / Ivy
/*
* (C) Copyright 2016-2021, 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.interfaces.*;
import org.jgrapht.alg.util.*;
import java.util.*;
import java.util.stream.*;
/**
* A base implementation for the computation of a fundamental cycle basis of a graph. Subclasses
* should only provide a method for constructing a spanning forest of the graph. A cycle basis is
* fundamental if and only if each cycle in the basis contains at least one edge which is not
* contained in any other cycle in the basis.
*
*
* For information on algorithms and heuristics for the computation of fundamental cycle bases see
* the following paper: Narsingh Deo, G. Prabhu, and M. S. Krishnamoorthy. Algorithms for Generating
* Fundamental Cycles in a Graph. ACM Trans. Math. Softw. 8, 1, 26-42, 1982.
*
*
* The implementation returns a fundamental cycle basis as an undirected cycle basis. For a
* discussion of different kinds of cycle bases in graphs see the following paper: Christian
* Liebchen, and Romeo Rizzi. Classes of Cycle Bases. Discrete Applied Mathematics, 155(3), 337-355,
* 2007.
*
* @param the vertex type
* @param the edge type
*
* @author Dimitrios Michail
*/
public abstract class AbstractFundamentalCycleBasis
implements
CycleBasisAlgorithm
{
protected Graph graph;
/**
* Constructor
*
* @param graph the input graph
*/
public AbstractFundamentalCycleBasis(Graph graph)
{
this.graph = GraphTests.requireDirectedOrUndirected(graph);
}
/**
* {@inheritDoc}
*/
@Override
public CycleBasis getCycleBasis()
{
// compute spanning forest
Map spanningForest = computeSpanningForest();
// collect set with all tree edges
Set treeEdges = spanningForest
.entrySet().stream().map(Map.Entry::getValue).filter(Objects::nonNull)
.collect(Collectors.toSet());
// build cycles for all non-tree edges
Set> cycles = new LinkedHashSet<>();
int length = 0;
double weight = 0d;
for (E e : graph.edgeSet()) {
if (!treeEdges.contains(e)) {
Pair, Double> c = buildFundamentalCycle(e, spanningForest);
cycles.add(c.getFirst());
length += c.getFirst().size();
weight += c.getSecond();
}
}
// return result
return new CycleBasisImpl<>(graph, cycles, length, weight);
}
/**
* Compute a spanning forest of the graph.
*
*
* The representation assumes that the map contains the predecessor edge of each vertex in the
* forest. The predecessor edge is the forest edge that was used to discover the vertex. If no
* such edge was used (the vertex is a leaf in the forest) then the corresponding entry must be
* null.
*
* @return a map representation of a spanning forest.
*/
protected abstract Map computeSpanningForest();
/**
* Given a non-tree edge and a spanning tree (forest) build a fundamental cycle.
*
* @param e a non-tree (forest) edge
* @param spanningForest the spanning tree (forest)
* @return a fundamental cycle
*/
private Pair, Double> buildFundamentalCycle(E e, Map spanningForest)
{
V source = graph.getEdgeSource(e);
V target = graph.getEdgeTarget(e);
// handle self-loops
if (source.equals(target)) {
return Pair.of(Collections.singletonList(e), graph.getEdgeWeight(e));
}
/*
* traverse half cycle
*/
Set path1 = new LinkedHashSet<>();
path1.add(e);
V cur = source;
while (!cur.equals(target)) {
E edgeToParent = spanningForest.get(cur);
if (edgeToParent == null) {
break;
}
V parent = Graphs.getOppositeVertex(graph, edgeToParent, cur);
path1.add(edgeToParent);
cur = parent;
}
/*
* traverse the other half cycle, while removing common edges
*/
double path2Weight = 0d;
LinkedList path2 = new LinkedList<>();
if (!cur.equals(target)) {
cur = target;
while (true) {
E edgeToParent = spanningForest.get(cur);
if (edgeToParent == null) {
break;
}
V parent = Graphs.getOppositeVertex(graph, edgeToParent, cur);
if (path1.contains(edgeToParent)) {
path1.remove(edgeToParent);
} else {
path2.add(edgeToParent);
path2Weight += graph.getEdgeWeight(edgeToParent);
}
cur = parent;
}
}
// now build cycle
for (E a : path1) {
path2Weight += graph.getEdgeWeight(a);
path2.addFirst(a);
}
return Pair.of(path2, path2Weight);
}
}