/*
* EuroCarbDB, a framework for carbohydrate bioinformatics
*
* Copyright (c) 2006-2009, Eurocarb project, or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
* A copy of this license accompanies this distribution in the file LICENSE.txt.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* Last commit: $Rev: 1423 $ by $Author: glycoslave $ on $Date:: 2009-07-05 #$
*/
package org.eurocarbdb.util.graph;
import java.util.Set;
import java.util.HashSet;
import java.util.List;
import java.util.ArrayList;
import java.util.Collections;
import org.eurocarbdb.util.Visitor;
/**
* A collection of mathematical and utility methods for Graphs.
*
* @author mjh
*/
public final class Graphs
{
/**
* Returns an unmodifiable version of the passed Graph.
*/
public static final <E,V> Graph<E,V> unmodifiableGraph( Graph<E,V> g )
{
// TODO - override all the modification methods.
return g;
}
/* isConnected *//*********************************************
*
* Returns true if this graph is fully <em>connected</em>, that is,
* there is at least 1 or more paths connecting every vertex in
* the graph. Boundary cases are when the graph is empty, in which
* case this method returns <tt>false</tt>, and when the graph
* contains a single vertex, in which case this method is defined
* to return <tt>true</tt>.
*/
public static final <E,V> boolean isConnected( Graph<E,V> g )
{
// eliminate simple edge cases
switch ( g.countVertices() )
{
case 0: return false;
case 1: return true;
}
// optimisation: if the number of edges exceeds the number of vertices
// by 1 or more, then there must be *at least* (#vertices - #edges)
// unconnected subgraphs.
if ( g.countVertices() - g.countEdges() > 1 )
return false;
// walk graph to identify any unconnected portion
ConnectedGraphVisitor<E,V> visitor = new ConnectedGraphVisitor<E,V>();
visitor.visit( g );
return visitor.isConnected;
}
/* getConnectedSubgraphs *//***********************************
*
* Returns the list of <em>fully connected</em> subgraphs in the
* passed graph, that is, graphs within this graph for which
* there is at least 1 or more paths connecting every vertex.
*
* @see #isConnected
*/
public static final <E,V> List<Graph<E,V>> getConnectedSubgraphs( Graph<E,V> g )
{
switch ( g.countVertices() )
{
case 0: return Collections.emptyList();
case 1: return Collections.singletonList( g );
}
ConnectedSubgraphVisitor<E,V> visitor
= new ConnectedSubgraphVisitor<E,V>();
visitor.visit( g );
return visitor.subgraphs;
}
/**
* Returns a {@link List} of all {@link Path}s from the
* {@link Graph#getRootVertex root vertex} to all
* {@link Graph#getLeafVertices leaves} in the given {@link Graph}.
*/
public static final <E,V> List<Path<E,V>> getPaths( Graph<E,V> g )
{
if ( g.size() == 0 )
return Collections.emptyList();
List<Path<E,V>> paths = new ArrayList<Path<E,V>>();
Set<Vertex<E,V>> leaves = g.getLeafVertices();
Vertex<E,V> root = g.getRootVertex();
List<Edge<E,V>> inc_edges;
Vertex<E,V> parent;
for ( Vertex<E,V> leaf : leaves )
{
Path<E,V> path = new Path<E,V>( g );
Vertex<E,V> p = leaf;
while ( true )
{
path.add( p );
if ( p == root )
break;
inc_edges = p.getIncomingEdges();
// assume only trees for now, therefore only 1 parent.
if ( inc_edges.size() != 1 )
throw new UnsupportedOperationException(
"this method only works with connected trees, not graphs for now");
parent = inc_edges.get(0).getParent();
if ( p == parent )
break;
path.add( inc_edges.get(0) );
p = parent;
}
path.reverse();
paths.add( path );
}
return paths;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~ INNER CLASSES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/** Utility class for walking a graph to test for connectivity. */
private static class ConnectedGraphVisitor<E,V> extends GraphVisitor<E,V>
{
/** Populated by accept(Graph). */
public boolean isConnected;
public void accept( Graph<E,V> g )
{
isConnected = true;
if ( g.isEmpty() )
{
log.debug("graph is empty, returning...");
return;
}
log.debug("visiting graph of size=" + g.size() );
visit( g.getRootVertex() );
if ( visitedVertices.size() < g.countVertices() )
isConnected = false;
}
}
/** Utility class for walking a graph to build fully connected subgraphs. */
private static class ConnectedSubgraphVisitor<E,V> extends GraphVisitor<E,V>
{
/** List of fully connected subgraphs. */
private List<Graph<E,V>> subgraphs;
/** Walk all vertices and edges of the passed graph. */
public void accept( Graph<E,V> g )
{
unvisitedVertices = new HashSet<Vertex<E,V>>( g.getAllVertices() );
subgraphs = new ArrayList<Graph<E,V>>();
Vertex<E,V> cursor;
while ( unvisitedVertices.size() > 0 )
{
cursor = unvisitedVertices.iterator().next();
//System.err.println("iterating over " + cursor );
visitedEdges = new HashSet<Edge<E,V>>();
visitedVertices = new HashSet<Vertex<E,V>>();
visit( cursor );
Graph<E,V> subgraph = new Graph<E,V>( visitedVertices, visitedEdges );
subgraphs.add( subgraph );
}
}
public List<Graph<E,V>> getConnectedSubgraphs()
{
assert subgraphs != null;
return subgraphs;
}
}
}