blob: dfda8ac0b8a9927caa4b3bc83465008720cb853a [file] [log] [blame]
package org.apache.commons.graph.scc;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import static org.apache.commons.graph.utils.Assertions.checkNotNull;
import static org.apache.commons.graph.utils.Assertions.checkState;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.apache.commons.graph.DirectedGraph;
import org.apache.commons.graph.RevertedGraph;
/**
* Implements the classical Kosaraju's algorithm to find the strongly connected components
*
* @param <V> the Graph vertices type.
* @param <E> the Graph edges type.
* @param <G> the directed graph type
*/
final class KosarajuSharirAlgorithm<V, E>
implements SccAlgorithm<V>
{
/** The graph. */
private final DirectedGraph<V, E> graph;
/**
* Create a new {@link KosarajuSharirAlgorithm} instance for the given {@link org.apache.commons.graph.Graph}.
*
* @param graph the {@link org.apache.commons.graph.Graph} on which to apply the algorithm
*/
public KosarajuSharirAlgorithm( final DirectedGraph<V, E> graph )
{
this.graph = graph;
}
/**
* Applies the classical Kosaraju's algorithm to find the strongly connected components of
* a vertex <code>source</code>.
*
* @param source the source vertex to start the search from
* @return the input graph strongly connected component.
*/
public Set<V> perform( final V source )
{
checkNotNull( source, "Kosaraju Sharir algorithm cannot be calculated without expressing the source vertex" );
checkState( graph.containsVertex( source ), "Vertex %s does not exist in the Graph", source );
final Set<V> visitedVertices = new HashSet<V>();
final List<V> expandedVertexList = getExpandedVertexList( source, visitedVertices );
final DirectedGraph<V, E> reverted = new RevertedGraph<V, E>( graph );
// remove the last element from the expanded vertices list
final V v = expandedVertexList.remove( expandedVertexList.size() - 1 );
final Set<V> sccSet = new HashSet<V>();
searchRecursive( reverted, v, sccSet, visitedVertices, false );
return sccSet;
}
/**
* Applies the classical Kosaraju's algorithm to find the strongly connected components.
*
* @return the input graph strongly connected component.
*/
public Set<Set<V>> perform()
{
final Set<V> visitedVertices = new HashSet<V>();
final List<V> expandedVertexList = getExpandedVertexList( null, visitedVertices );
final DirectedGraph<V, E> reverted = new RevertedGraph<V, E>( graph );
final Set<Set<V>> sccs = new HashSet<Set<V>>();
// insert the expanded vertices in reverse order into a linked hash set
// this is needed to quickly remove already found SCCs from the stack
final LinkedHashSet<V> stack = new LinkedHashSet<V>();
for ( int i = expandedVertexList.size() - 1; i >= 0; i-- )
{
stack.add( expandedVertexList.get( i ) );
}
while ( stack.size() > 0 )
{
// remove the last element from the expanded vertices list
final V v = stack.iterator().next();
final Set<V> sccSet = new HashSet<V>();
searchRecursive( reverted, v, sccSet, visitedVertices, false );
// remove all strongly connected components from the expanded list
stack.removeAll( sccSet );
sccs.add( sccSet );
}
return sccs;
}
/**
* Performs a depth-first search to create a recursive vertex list.
*
* @param source the starting vertex
* @param visitedVertices a {@link Set} containing all visited vertices
* @return the recursively expanded vertex list for Kosaraju's algorithm
*/
private List<V> getExpandedVertexList( final V source, final Set<V> visitedVertices )
{
final int size = ( source != null ) ? 13 : graph.getOrder();
final Set<V> vertices = new HashSet<V>( size );
if ( source != null )
{
vertices.add( source );
}
else
{
for ( V vertex : graph.getVertices() )
{
vertices.add( vertex );
}
}
// use an ArrayList so that subList is fast
final ArrayList<V> expandedVertexList = new ArrayList<V>();
int idx = 0;
while ( ! vertices.isEmpty() )
{
// get the next vertex that has not yet been added to the expanded list
final V v = vertices.iterator().next();
searchRecursive( graph, v, expandedVertexList, visitedVertices, true );
// remove all expanded vertices from the list of vertices that have to be
// still processed. To improve performance, only the items that have been
// added to the list since the last iteration are removed
vertices.removeAll( expandedVertexList.subList( idx, expandedVertexList.size() ) );
idx = expandedVertexList.size();
}
return expandedVertexList;
}
/**
* Searches a directed graph in iterative depth-first order, while adding the visited
* vertices in a recursive manner, i.e. a vertex is added to the result list only
* when the search has finished expanding the vertex (and its subsequent childs).
*
* <p><b>Implementation Note:</b> in the first step we look for vertices that have not
* been visited yet, while in the second step we search for vertices that have already
* been visited.</p>
* @param g the graph to be search
* @param source the start vertex
* @param coll the recursive collection of visited vertices
* @param visited contains vertices that have been already visited
* @param forward <code>true</code> for the first step of Kosaraju's algorithm,
* <code>false</code> for the second step.
*/
private void searchRecursive( final DirectedGraph<V, E> g, final V source,
final Collection<V> coll, final Set<V> visited,
final boolean forward )
{
final LinkedList<V> stack = new LinkedList<V>();
stack.addLast( source );
while ( !stack.isEmpty() )
{
final V v = stack.removeLast();
// if the vertex has already been visited it can be put into the
// collection, as we are now finished expanding this vertex
// the if takes both cases into account:
// * step1: forward && visited.contains(v)
// * step2: !forward && !visited.contains(v)
if ( ! ( forward ^ visited.contains( v ) ) )
{
coll.add( v );
continue;
}
// add the current vertex to the stack, so it is visited again
// when all connected vertices have been visited
stack.addLast( v );
if ( forward )
{
visited.add( v );
}
else
{
visited.remove( v );
}
// add all not yet visited vertices that can be reached from this
// vertex to the stack
for ( V w : g.getOutbound( v ) )
{
if ( ! ( forward ^ ! visited.contains( w ) ) )
{
stack.addLast( w );
}
}
}
}
}