package rpi.goldsd.graph; import java.util.Enumeration; import java.util.Stack; import rpi.goldsd.container.*; /** * The abstract GraphBase class provides many of the fundamental * methods of a graph container. The Graph and Tree * classes are subclasses of the abstract GraphBase class. * * @see Vertex * @see Edge * @see DirectedEdge * @see Graph * @see Tree * @version 2.1, 5/12/98 * @author David Goldschmidt */ public abstract class GraphBase implements Cloneable, Drawable { /* ****************** STATIC CLASS ATTRIBUTES ****************** */ /** The default name of a graph. */ public static final String DEFAULT_NAME = "no-name"; /* ****************** CONSTRUCTOR METHODS ****************** */ /** * Constructs an empty graph with the specified name, initial * vertex hashtable size, and initial edge hashtable size. * @param name the user-defined name of this graph. * @param vertexTableSize the initial hashtable size for the hashtable * used to contain the set of vertices. * @param edgeTableSize the initial hashtable size for the * hashtable used to contain the set of edges. */ public GraphBase( String name, int vertexTableSize, int edgeTableSize ) { this.name = name; this.vertices = new Table( vertexTableSize ); this.edges = new Table( edgeTableSize ); } /** * Constructs an empty graph with the specified initial vertex * hashtable size and initial edge hashtable size. * @param vertexTableSize the initial hashtable size for the hashtable * used to contain the set of vertices. * @param edgeTableSize the initial hashtable size for the * hashtable used to contain the set of edges. */ public GraphBase( int vertexTableSize, int edgeTableSize ) { this( DEFAULT_NAME, vertexTableSize, edgeTableSize ); } /** * Constructs an empty graph with the specified name. * @param name the user-defined name of this graph. */ public GraphBase( String name ) { this( name, Table.DEFAULT_SIZE, Table.DEFAULT_SIZE ); } /** Constructs an unnamed empty graph. */ public GraphBase() { this( DEFAULT_NAME ); } /** * Constructs a graph by copying another graph. * @param G the graph to copy. */ public GraphBase( GraphBase G ) { this( "Copy of " + G.name(), G.vertices.getTableSize(), G.edges.getTableSize() ); graphCopyHelper( this, G ); } /** * Clones this graph. Classes that extend the GraphBase * class must implement the clone() method. * @return a new graph that is a clone of this graph. */ public abstract Object clone(); /** * This protected static method performs a deep copy of all edges * and vertices in the graph source to the graph target. Note * that the target graph is assumed to be constructed, with * the name, vertices, and edges properly initialized (i.e. the * two Table objects should be empty). * @param target the target graph object (should be an empty graph). * @param source the source graph object. */ protected static final void graphCopyHelper( GraphBase target, GraphBase source ) { Enumeration e = source.vertices(); while ( e.hasMoreElements() ) { Vertex j = (Vertex)e.nextElement(); target.addBase( (Vertex)j.clone() ); } e = source.vertices(); while ( e.hasMoreElements() ) { Vertex q = (Vertex)e.nextElement(); Enumeration w = source.vertices(); while ( w.hasMoreElements() ) { Vertex r = (Vertex)w.nextElement(); if ( q != r ) { Edge E = source.edgeBetween( q, r ); if ( E != null ) { Vertex qq = target.mapToVertex( q.data() ); Vertex rr = target.mapToVertex( r.data() ); if ( ! target.isEdgeBetween( qq, rr ) ) target.addBase( new Edge( qq, rr, E.data() ) ); } } } } } /* ****************** ACCESSOR METHODS ****************** */ /** * Tests if a given edge exists in this graph. * @param E the edge to test. * @return true if the given edge E is in this graph; * false otherwise. */ public boolean contains( Edge E ) { return ( edges.contains( E.data() ) ); } /** * Tests if a given vertex exists in this graph. * @param V the vertex to test. * @return true if the given vertex V is in this graph; * false otherwise. */ public boolean contains( Vertex V ) { return ( vertices.contains( V.data() ) ); } /** * Finds an edge of this graph that is between the two given vertices. * The two vertices and the corresponding edge, if present, must be part * of this graph for this method to return an Edge object. * @param V1 the first vertex of the pair. * @param V2 the second vertex of the pair. * @return the Edge object that is between the two given vertices * V1 and V2; or null if no such edge * exists. */ public Edge edgeBetween( Vertex V1, Vertex V2 ) { //issue with V1 being tail of directed edge ... if ( ! vertices.contains( V1.data() ) || ! vertices.contains( V2.data() ) ) return null; Enumeration incidentEdges = V1.incidentEdges(); while ( incidentEdges.hasMoreElements() ) { Edge E = (Edge)incidentEdges.nextElement(); if ( E.traverseFrom( V1 ) == V2 ) return E; } incidentEdges = V2.incidentEdges(); while ( incidentEdges.hasMoreElements() ) { Edge E = (Edge)incidentEdges.nextElement(); if ( E.traverseFrom( V2 ) == V1 ) return E; } return null; } /** * Returns an enumeration of the edges in this graph. * @return an enumeration of the edges in this graph. */ public Enumeration edges() { return edges.elements(); } /** * Tests if this graph is a directed graph. * @return true if this graph is a directed graph (i.e. contains * directed edges); false otherwise. */ public boolean isDigraph() { Enumeration e = edges(); if ( e.hasMoreElements() ) return ( ((Edge)e.nextElement()) instanceof DirectedEdge ); else return false; } /** * Tests if an edge exists in this graph that is between the two given * vertices. The two vertices and the corresponding edge, if present, * must be part of this graph for this method to return true. * @param V1 the first vertex of the pair. * @param V2 the second vertex of the pair. * @return true if an edge exists in this graph that is between the * two given vertices V1 and V2; false * otherwise. */ public boolean isEdgeBetween( Vertex V1, Vertex V2 ) { return ( edgeBetween( V1, V2 ) != null ); } /** * Tests if this graph contains any vertices. * @return true if this graph contains no vertices; * false otherwise. */ public boolean isEmpty() { return vertices.isEmpty(); } /** * Tests if this graph is a weighted graph. * @return true if this graph is a weighted graph (i.e. contains * weighted edges); false otherwise. */ public boolean isWeighted() { Enumeration e = edges(); if ( e.hasMoreElements() ) return ( ((Edge)e.nextElement()).isWeighted() ); else return false; } /** * Maps the given key to an edge in this graph, if such a mapping * exists in the underlying hashtable. * @param key the unique key identifying an edge. * @return the edge identified by the given key; or null * if key does not map to an edge of this graph. */ public Edge mapToEdge( Hashable key ) { return ( (Edge)edges.map( key ) ); } /** * Maps the given key to a vertex in this graph, if such a mapping * exists in the underlying hashtable. * @param key the unique key identifying a vertex. * @return the vertex identified by the given key; or null * if key does not map to a vertex of this graph. */ public Vertex mapToVertex( Hashable key ) { return ( (Vertex)vertices.map( key ) ); } /** * Returns the user-defined name associated with this graph. * @return the user-defined name associated with this graph. */ public String name() { return name; } /** * Returns the number of edges in this graph. * @return the number of edges in this graph. */ public int numEdges() { return edges.getSize(); } /** * Returns the number of vertices in this graph. * @return the number of vertices in this graph. */ public int numVertices() { return vertices.getSize(); } /** * Displays basic information about this graph. * @param printVertices if set to true, this method displays summary * information for each of the vertices in this graph. */ public void printSummary( boolean printVertices ) { System.out.println( name + ":" ); if ( isEmpty() ) System.out.println( " contains no vertices or edges." ); else { System.out.println( " # of vertices: " + numVertices() ); System.out.print( " # of " ); if ( ! isDigraph() ) System.out.print( "un" ); System.out.println( "directed edges: " + numEdges() ); if ( printVertices ) { System.out.println( " Vertices and incident edges:" ); Enumeration e = vertices(); while ( e.hasMoreElements() ) ((Vertex)e.nextElement()).printSummary(); } } } /** Displays basic information about this graph. */ public void printSummary() { printSummary( true ); } /** * Returns a String representation of this graph (to keep output * concise, the String representation currently consists of the * name of the graph). * @return a String representation of this graph. */ public String toString() { return name; } /** * Returns an enumeration of the vertices in this graph. * @return an enumeration of the vertices in this graph. */ public Enumeration vertices() { return vertices.elements(); } /* ****************** MUTATOR METHODS ****************** */ /** * Adds the given edge to this graph if it is not already * in this graph. The two endpoint vertices associated with * the given edge must already be in this graph. * @param E the edge to be added to this graph. * @exception rpi.goldsd.container.DuplicateElementException if * the key is already present in the set of vertices. * @exception rpi.goldsd.graph.InAnotherGraphException if the * edge is already in a different graph. * @exception rpi.goldsd.graph.VertexNotFoundException if either * of the endpoint vertices of the edge are not in * this graph. */ protected synchronized void addBase( Edge E ) throws rpi.goldsd.container.DuplicateElementException, InAnotherGraphException, VertexNotFoundException { if ( E.isInGraph( this ) ) throw new DuplicateElementException( "Duplicate edge: " + E ); else if ( E.isInGraph() ) throw new InAnotherGraphException( "Edge " + E + " is in another graph." ); else if ( numEdges() > 0 && E instanceof DirectedEdge && ! isDigraph() ) throw new IllegalArgumentException( "Cannot add directed edge to undirected graph." ); else if ( numEdges() > 0 && ! ( E instanceof DirectedEdge ) && isDigraph() ) throw new IllegalArgumentException( "Cannot add undirected edge to digraph." ); Vertex startVertex = E.startVertex(); Vertex endVertex = E.endVertex(); if ( ! contains( startVertex ) ) throw new VertexNotFoundException( "Vertex " + startVertex + " not in graph." ); else if ( ! contains( endVertex ) ) throw new VertexNotFoundException( "Vertex " + endVertex + " not in graph." ); edges.add( E ); E.setInGraph( this ); startVertex.addIncidentEdge( E ); if ( ! E.isSelfLoop() && ! ( E instanceof DirectedEdge ) ) endVertex.addIncidentEdge( E ); if ( E instanceof DirectedEdge ) endVertex.addIncomingEdge( (DirectedEdge)E ); } /** * Adds the given vertex to this graph if it is not already in this graph. * @param V the vertex to be added to this graph. * @exception rpi.goldsd.container.DuplicateElementException if * the key is already present in the set of vertices. * @exception rpi.goldsd.graph.InAnotherGraphException if the * vertex is already in a different graph. */ protected synchronized void addBase( Vertex V ) throws rpi.goldsd.container.DuplicateElementException, InAnotherGraphException { if ( V.isInGraph( this ) ) throw new DuplicateElementException( "Duplicate vertex: " + V ); else if ( V.isInGraph() ) throw new InAnotherGraphException( "Vertex " + V + " is in another graph." ); vertices.add( V ); V.setInGraph( this ); } /** Removes all vertices and edges from this graph. */ public synchronized void clear() { Enumeration e = edges(); while ( e.hasMoreElements() ) ((Edge)e.nextElement()).setInGraph( null ); e = vertices(); while ( e.hasMoreElements() ) { Vertex V = (Vertex)e.nextElement(); V.removeAllIncidentEdges(); V.removeAllIncomingEdges(); V.setInGraph( null ); } edges.clear(); vertices.clear(); } /** * Rehashes an existing Edge object that is part of this graph, * if necessary. * @param oldData the old Hashable object used to locate the * old Edge object in the underlying hashtable. * @param newData the new Hashable object that should replace * the old Hashable object associated with the * given Edge object. * @param E the Edge object that contains the old Hashable * object oldData. */ protected void rehash( Hashable oldData, Hashable newData, Edge E ) { int tableSize = edges.getTableSize(); if ( oldData.hash( tableSize ) != newData.hash( tableSize ) ) { edges.remove( oldData ); E.data = newData; edges.add( E.data(), E ); } } /** * Rehashes an existing Vertex object that is part of this graph, * if necessary. * @param oldData the old Hashable object used to locate the * old Vertex object in the underlying hashtable. * @param V the Vertex object that contains a new Hashable * object. */ protected void rehash( Hashable oldData, Hashable newData, Vertex V ) { int tableSize = vertices.getTableSize(); if ( oldData.hash( tableSize ) != newData.hash( tableSize ) ) { vertices.remove( oldData ); V.data = newData; vertices.add( V.data(), V ); } } /** * Sets the name of this graph to the given String argument. * @param name the new name of this graph. * @return the old name of this graph. */ public String setName( String name ) { String s = this.name; this.name = name; return s; } /* ****************** CLASS ATTRIBUTES ****************** */ /** Hashtable containing references to all edges in this graph. */ protected Table edges; /** A reference to the user-defined name of this graph. */ protected String name; /** Hashtable containing references to all vertices in this graph. */ protected Table vertices; }