org.jgrapht.alg.cycle.ChordalityInspector Maven / Gradle / Ivy
/*
* (C) Copyright 2018-2023, by Timofey Chudakov 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.graph.*;
import org.jgrapht.traverse.*;
import org.jgrapht.util.*;
import java.util.*;
/**
* Tests whether a graph is chordal. A
* chordal graph is a simple graph in which all
* cycles of four or more vertices have
* a chord. A chord is an edge that is
* not part of the cycle but connects two vertices of the cycle. A graph is chordal if and only if
* it has a
* perfect elimination order. A perfect elimination order in a graph is an ordering of the
* vertices of the graph such that, for each vertex $v$, $v$ and the neighbors of $v$ that occur
* after $v$ in the order form a clique. This implementation uses either
* {@link MaximumCardinalityIterator} or {@link LexBreadthFirstIterator} to compute a perfect
* elimination order. The desired method is specified during construction time.
*
* Chordal graphs are a subset of the
* perfect graphs. They may be recognized in polynomial time, and several problems that are hard
* on other classes of graphs such as minimum vertex coloring or determining maximum cardinality
* cliques and independent set can be performed in polynomial time when the input is chordal.
*
* All methods in this class run in $\mathcal{O}(|V| + |E|)$ time. Determining whether a graph is
* chordal, as well as computing a perfect elimination order takes $\mathcal{O}(|V| + |E|)$ time,
* independent of the algorithm ({@link MaximumCardinalityIterator} or
* {@link LexBreadthFirstIterator}) used to compute the perfect elimination order.
*
* All the methods in this class are invoked in a lazy fashion, meaning that computations are only
* started once the method gets invoked.
*
* @param the graph vertex type.
* @param the graph edge type.
*
* @author Timofey Chudakov
*/
public class ChordalityInspector
{
/**
* Stores the type of iterator used by this {@code ChordalityInspector}.
*/
private final IterationOrder iterationOrder;
/**
* Iterator used for producing perfect elimination order.
*/
private final GraphIterator orderIterator;
/**
* The inspected graph.
*/
private final Graph graph;
/**
* Contains true if the graph is chordal, otherwise false.
*/
private boolean chordal = false;
/**
* Order produced by {@code orderIterator}.
*/
private List order;
/**
* A hole contained in the inspected {@code graph}.
*/
private GraphPath hole;
/**
* Creates a chordality inspector for {@code graph}, which uses
* {@link MaximumCardinalityIterator} as a default iterator.
*
* @param graph the graph for which a chordality inspector to be created.
*/
public ChordalityInspector(Graph graph)
{
this(graph, IterationOrder.MCS);
}
/**
* Creates a chordality inspector for {@code graph}, which uses an iterator defined by the
* second parameter as an internal iterator.
*
* @param graph the graph for which a chordality inspector is to be created.
* @param iterationOrder the constant, which defines iterator to be used by this
* {@code ChordalityInspector}.
*/
public ChordalityInspector(Graph graph, IterationOrder iterationOrder)
{
Objects.requireNonNull(graph);
if (graph.getType().isDirected()) {
this.graph = new AsUndirectedGraph<>(graph);
} else {
this.graph = graph;
}
this.iterationOrder = iterationOrder;
this.hole = null;
if (iterationOrder == IterationOrder.MCS) {
this.orderIterator = new MaximumCardinalityIterator<>(graph);
} else {
this.orderIterator = new LexBreadthFirstIterator<>(graph);
}
}
/**
* Checks whether the inspected graph is chordal.
*
* @return true if this graph is chordal, otherwise false.
*/
public boolean isChordal()
{
if (order == null) {
order = Collections.unmodifiableList(lazyComputeOrder());
chordal = isPerfectEliminationOrder(order, true);
}
return chordal;
}
/**
* Returns a
* perfect elimination order if one exists. The existence of a perfect elimination order
* certifies that the graph is chordal. This method returns null if the graph is not chordal.
*
* @return a perfect elimination order of a graph or null if graph is not chordal.
*/
public List getPerfectEliminationOrder()
{
isChordal();
if (chordal) {
return order;
}
return null;
}
/**
* A graph which is not chordal, must contain a
* hole (chordless cycle on 4 or more
* vertices). The existence of a hole certifies that the graph is not chordal. This method
* returns a chordless cycle if the graph is not chordal, or null if the graph is chordal.
*
* @return a hole if the {@code graph} is not chordal, or null if the graph is chordal.
*/
public GraphPath getHole()
{
isChordal();
return hole;
}
/**
* Checks whether the vertices in the {@code vertexOrder} form a
* perfect elimination order with respect to the inspected graph. Returns false otherwise.
*
* @param vertexOrder the sequence of vertices of the {@code graph}.
* @return true if the {@code graph} is chordal and the vertices in {@code vertexOrder} are in
* perfect elimination order, otherwise false.
*/
public boolean isPerfectEliminationOrder(List vertexOrder)
{
return isPerfectEliminationOrder(vertexOrder, false);
}
/**
* Computes vertex order via {@code orderIterator}.
*
* @return computed order.
*/
private List lazyComputeOrder()
{
if (order == null) {
int vertexNum = graph.vertexSet().size();
order = new ArrayList<>(vertexNum);
for (int i = 0; i < vertexNum; i++) {
order.add(orderIterator.next());
}
}
return order;
}
/**
* Checks whether the vertices in the {@code vertexOrder} form a
* perfect elimination order with respect to the inspected graph. Returns false otherwise.
* Computes a hole if the {@code computeHole} is true.
*
* @param vertexOrder the sequence of vertices of {@code graph}.
* @param computeHole tells whether to compute the hole if the graph isn't chordal.
* @return true if the {@code graph} is chordal and the vertices in {@code vertexOrder} are in
* perfect elimination order.
*/
private boolean isPerfectEliminationOrder(List vertexOrder, boolean computeHole)
{
Set graphVertices = graph.vertexSet();
if (graphVertices.size() == vertexOrder.size() && graphVertices.containsAll(vertexOrder)) {
Map vertexInOrder = getVertexInOrder(vertexOrder);
for (V vertex : vertexOrder) {
Set predecessors = getPredecessors(vertexInOrder, vertex);
if (predecessors.size() > 0) {
V maxPredecessor =
Collections.max(predecessors, Comparator.comparingInt(vertexInOrder::get));
for (V predecessor : predecessors) {
if (!predecessor.equals(maxPredecessor)
&& !graph.containsEdge(predecessor, maxPredecessor))
{
if (computeHole) {
// predecessor, vertex and maxPredecessor are vertices, which lie
// consecutively on
// some chordless cycle in the graph
findHole(predecessor, vertex, maxPredecessor);
}
return false;
}
}
}
}
return true;
} else {
return false;
}
}
/**
* Returns a map containing vertices from the {@code vertexOrder} mapped to their indices in
* {@code vertexOrder}.
*
* @param vertexOrder a list with vertices.
* @return a mapping of vertices from {@code vertexOrder} to their indices in
* {@code vertexOrder}.
*/
private Map getVertexInOrder(List vertexOrder)
{
Map vertexInOrder =
CollectionUtil.newHashMapWithExpectedSize(vertexOrder.size());
int i = 0;
for (V vertex : vertexOrder) {
vertexInOrder.put(vertex, i++);
}
return vertexInOrder;
}
/**
* Computes a hole from the vertices of {@code subgraph} of the inspected {@code graph} with
* vertices {@code a}, {@code b} and {@code c} on this cycle (there must be no edge between
* {@code a} and {@code c}.
*
* @param a vertex that belongs to the cycle
* @param b vertex that belongs to the cycle
* @param c vertex that belongs to the cycle
*/
private void findHole(V a, V b, V c)
{
// b is the first vertex in the order produced by the iterator whose predecessors don't form
// a clique.
// a and c are a pair of vertices, which are predecessors of b and are not adjacent. These
// three vertices
// belong to some chordless cycle in the G[S] where G[S] is a subgraph of G on vertices in
// S = {u : index_in_order(u) <= index_in_order(v)}.
// this method uses dfs to find any cycle in G, in which every vertex isn't adjacent to b,
// except for a and b.
// then it finds a chordless subcycle in linear time and returns it.
List cycle = new ArrayList<>(Arrays.asList(a, b, c));
Map visited =
CollectionUtil.newHashMapWithExpectedSize(graph.vertexSet().size());
for (V vertex : graph.vertexSet()) {
visited.put(vertex, false);
}
visited.put(a, true);
visited.put(b, true);
dfsVisit(cycle, visited, a, b, c);
cycle = minimizeCycle(cycle);
hole = new GraphWalk<>(graph, cycle, 0);
}
/**
* Computes some cycle in the graph on the vertices from the domain of the map {@code visited}.
* More precisely, finds some path from {@code middle} to {@code finish}. The vertex
* {@code middle} isn't the endpoint of any chord in this cycle.
*
* @param cycle already computed part of the cycle
* @param visited the map that defines which vertex has been visited by this method
* @param finish the last vertex in the cycle.
* @param middle the vertex, which must be adjacent onl
* @param current currently examined vertex.
*/
private void dfsVisit(List cycle, Map visited, V finish, V middle, V current)
{
visited.put(current, true);
for (E edge : graph.edgesOf(current)) {
V opposite = Graphs.getOppositeVertex(graph, edge, current);
if ((!visited.get(opposite) && !graph.containsEdge(opposite, middle))
|| opposite.equals(finish))
{
cycle.add(opposite);
if (opposite.equals(finish)) {
return;
}
dfsVisit(cycle, visited, finish, middle, opposite);
if (cycle.get(cycle.size() - 1).equals(finish)) {
return;
} else {
cycle.remove(cycle.size() - 1);
}
}
}
}
/**
* Minimizes the cycle represented by the list {@code cycle}. More precisely it retains first 2
* vertices and finds a chordless cycle starting from the third vertex.
*
* @param cycle vertices of the graph that represent the cycle.
* @return a chordless cycle
*/
private List minimizeCycle(List cycle)
{
Set cycleVertices = new HashSet<>(cycle);
cycleVertices.remove(cycle.get(1));
List minimized = new ArrayList<>();
minimized.add(cycle.get(0));
minimized.add(cycle.get(1));
for (int i = 2; i < cycle.size() - 1;) {
V vertex = cycle.get(i);
minimized.add(vertex);
cycleVertices.remove(vertex);
Set forward = new HashSet<>();
// compute vertices with the higher index in the cycle
for (E edge : graph.edgesOf(vertex)) {
V opposite = Graphs.getOppositeVertex(graph, edge, vertex);
if (cycleVertices.contains(opposite)) {
forward.add(opposite);
}
}
// jump to the vertex with the highest index with respect to the current vertex
for (V forwardVertex : forward) {
if (cycleVertices.contains(forwardVertex)) {
do {
cycleVertices.remove(cycle.get(i));
i++;
} while (i < cycle.size() && !cycle.get(i).equals(forwardVertex));
}
}
}
minimized.add(cycle.get(cycle.size() - 1));
return minimized;
}
/**
* Returns the predecessors of {@code vertex} in the order defined by {@code map}. More
* precisely, returns those of {@code vertex}, whose mapped index in {@code map} is less then
* the index of {@code vertex}.
*
* @param vertexInOrder defines the mapping of vertices in {@code graph} to their indices in
* order.
* @param vertex the vertex whose predecessors in order are to be returned.
* @return the predecessors of {@code vertex} in order defines by {@code map}.
*/
private Set getPredecessors(Map vertexInOrder, V vertex)
{
Set predecessors = new HashSet<>();
Integer vertexPosition = vertexInOrder.get(vertex);
Set edges = graph.edgesOf(vertex);
for (E edge : edges) {
V oppositeVertex = Graphs.getOppositeVertex(graph, edge, vertex);
Integer destPosition = vertexInOrder.get(oppositeVertex);
if (destPosition < vertexPosition) {
predecessors.add(oppositeVertex);
}
}
return predecessors;
}
/**
* Returns the type of iterator used in this {@code ChordalityInspector}
*
* @return the type of iterator used in this {@code ChordalityInspector}
*/
public IterationOrder getIterationOrder()
{
return iterationOrder;
}
/**
* Specifies internal iterator type.
*/
public enum IterationOrder
{
MCS,
LEX_BFS,
}
}