[jgrapht-developers] Feedback on BipartiteGraph implementation
Brought to you by:
barak_naveh,
perfecthash
|
From: Lucas S. <lsc...@jp...> - 2007-10-07 03:13:22
|
All,
Please find an implementation of a BipartiteGraph attached. I've
seen this requested a few times in the archives and, since I need it
myself to build upon, I thought I'd post the implementation for
feedback and comments.
Here are some items I think could be up for debate:
1) I implemented BipartiteGraph as a GraphDelegator. This seemed to
be the path of least resistance. Is there a better place in the
class hierarchy to put this?
2) I track the membership of vertices to partitions by keeping a
private Map. This is potentially inefficient since Vertex object
references are duplicated. I can't come up with a more space-
efficient approach at the moment and this was "good enough" for a
first implementation.
3) The traversal in the constructor to build a bipartite graph from
the input graph is a bit messy. Is a TraversalListener the best way
to do this?
Most of the method are self-explanatory -- I just added checks to
ensure that the bipartite property holds after each method. I also
added additional methods for adding vertices to specific partitions
and getting the set of partition vertices. I have junit tests for
this code that I have not posted yet.
Obviously, there is quite a bit more documentation to write before
it's in sufficient shape for submission, but I thought it would be
good to get some feedback.
Thank you,
-Lucas
/**
* A bipartite delegator enforces the partitioning of the graph
vertices into
* two disjoint sets. An additional addVertex() method is added
which allows
* the assignment of vertices to a one partition or the other. The
addEdge()
* methods are checked and the addition will fail if the bipartite
constraints
* are violated
*
* @author Lucas J. Scharenbroich
* @since Oct. 5, 2007
*/
import java.util.*;
import org.jgrapht.*;
import org.jgrapht.graph.*;
import org.jgrapht.traverse.*;
import org.jgrapht.event.*;
public class BipartiteGraph<V, E> extends GraphDelegator<V, E>
{
public enum Partition {V1, V2};
protected final Map<Partition, Set<V>> vertexSetMapping =
new EnumMap<Partition, Set<V>>(Partition.class);
/**
* Constructor for BipartiteGraph.
*
* @param g the backing graph over which the bipartite topology
* is to be constrained
*/
public BipartiteGraph(final Graph<V, E> g)
{
super(g);
vertexSetMapping.put(Partition.V1, new HashSet<V>());
vertexSetMapping.put(Partition.V2, new HashSet<V>());
// Run through the current set of vertices and edges and
// place them arbitrarily is the two classes. This is
// really the best we can do with a general graph. If
// a bipartite violation is detected, throw an exception
//
// We can't just iterate over the edgeSet because this can
// fail for cases like the following
//
// e1 e2 e3
// v1 ----> v2 ----> v3 <---- v4
//
// If the edges are visited in the order e1, e3, e2, then
// we might partition the vertices as ((v1, v4), (v2, v3).
// When e2 is encountered, v2 and v3 are in the same set
// and the test fails, even though there is a bipartite
// paritioning of the vertices, e.g. ((v1, v3), (v2, v4))
GraphIterator<V, E> iterator = new DepthFirstIterator<V, E>(g);
iterator.addTraversalListener(new BipartiteListener(g));
while (iterator.hasNext()) {
iterator.next();
}
}
/**
* A small, private class to walk a graph and partition it into two
classes.
*/
private final class BipartiteListener extends
TraversalListenerAdapter<V, E>
{
private final Graph<V, E> g;
public BipartiteListener(Graph<V, E> g)
{
this.g = g;
}
public void edgeTraversed(EdgeTraversalEvent<V, E> e)
{
V source = g.getEdgeSource(e.getEdge());
V target = g.getEdgeTarget(e.getEdge());
Set<V> V1 = vertexSetMapping.get(Partition.V1);
Set<V> V2 = vertexSetMapping.get(Partition.V2);
boolean V1source = V1.contains(source);
boolean V1target = V1.contains(target);
boolean V2source = V2.contains(source);
boolean V2target = V2.contains(target);
// If both vertices are in either set, throw an exception
if ((V1source && V1target) || (V2source && V2target)) {
String msg = new StringBuffer()
.append( source )
.append(" and ")
.append(target)
.append( "are in the same parition").toString();
throw new IllegalArgumentException(msg) ;
}
// Make sure that at least one of the vertices has been seen
if (!( V1source || V2source || V1target || V2target)) {
String msg = "Free edge encountered in traversal";
throw new IllegalArgumentException(msg);
}
// If only the source has been seen, assign the target to the
other set
if (V1source && !(V1target || V2target)) {
vertexSetMapping.get(Partition.V2).add(target);
}
if (V2source && !(V1target || V2target)) {
vertexSetMapping.get(Partition.V1).add(target);
}
// If only the target has been seen, assign the source to the
other set
if (V1target && !(V1source || V2source))
vertexSetMapping.get(Partition.V2).add(source);
if (V2target && !( V1source || V2source))
vertexSetMapping.get(Partition.V1).add(source);
}
public void vertexTraversed(VertexTraversalEvent<V> e)
{
V v = e.getVertex();
// If this vertex has not been encountered before, assign it
// to the first partition
if (!vertexSetMapping.get(Partition.V1).contains(v) &&
!vertexSetMapping.get(Partition.V2).contains(v))
{
vertexSetMapping.get(Partition.V1).add(v);
}
}
}
/**
* Finds the partition set that the vertex belongs to. Return null if
* the vertex does not exist.
*
* @param v Vertex
* @return
*/
private Partition vertexPartition(V v)
{
for (Map.Entry<Partition, Set<V>> entry :
vertexSetMapping.entrySet())
if (entry.getValue().contains( v ))
return entry.getKey();
return null;
}
/**
* Check that the source and target vertices are not in the same
partition
* before joining them.
*/
@Override
public E addEdge(V sourceVertex, V targetVertex)
{
if (vertexPartition(sourceVertex).equals(vertexPartition
(targetVertex))) {
return null;
}
else {
return super.addEdge(sourceVertex, targetVertex);
}
}
@Override
public boolean addEdge(V sourceVertex, V targetVertex, E e)
{
if (vertexPartition(sourceVertex).equals(vertexPartition
(targetVertex))) {
String msg = "A Bipartite graph cannot join vertices in the
same set";
throw new IllegalArgumentException(msg);
}
return super.addEdge( sourceVertex, targetVertex, e );
}
@Override
public boolean addVertex(V v)
{
return addVertex(v, Partition.V1);
}
public boolean addVertex(V v, Partition partition)
{
boolean rval = super.addVertex(v);
if (rval) {
vertexSetMapping.get(partition).add(v);
}
return rval;
}
/**
* Find a vertex in a given partition
*
* @param v
* @param partition
* @return true if the vertex is in the partition
*/
public boolean containsVertex(V v, Partition partition)
{
return vertexSetMapping.get(partition).contains(v);
}
@Override
public boolean removeVertex(V v)
{
boolean rval = super.removeVertex(v);
if (rval) {
vertexSetMapping.get(vertexPartition(v)).remove(v);
}
return rval;
}
/**
* Get the set of all vertices in one of the partitions
* @param partition
* @return a set of vertices
*/
public Set<V> vertexSet(Partition partition)
{
return Collections.unmodifiableSet(vertexSetMapping.get
(partition));
}
}
|