appendix b java graph adt - university of auckland · appendix b java graph adt this appendix,...

31
Appendix B JAVA GRAPH ADT This appendix, written by Dr. Michael Dinneen in 1999, presents a simplified abstract class for representing a graph abstract data type (ADT). Although it is fully functional, it purposely omits most exception handling and other niceties that should be in any com- mercial level package. These details would distract from our overall (introductory) goal of showing how to implement a basic graph class in Java. Our plan is to have a common data structure that represents both graphs and digraphs. A graph will be a digraph with anti-parallel arcs; that is, if then also. The initial abstract class presented below requires a core set of methods needed for the realized graph ADT. It will be extended with the actual internal data structure representation in the form of adjacency matrix or adjacency lists (or whatever the designer picks). import java.util.Vector; import java.io.BufferedReader; /* * Current Abstract Data Type interface for (di)graph classes. */ abstract class GraphADT { // Need default constructor, copy and BufferedReader constructors // (commented since java doesn’t allow abstract constructors!) // // public GraphADT(); // public GraphADT(GraphADT); // public GraphADT(BufferedReader in); Right from the beginning we get in trouble since Java does not allow abstract construc- tors. We will leave these as comments and hope the graph class designer will abide by them. We want to create graphs from an empty graph, copy an existing graph, or read in 233

Upload: buihanh

Post on 17-Mar-2019

222 views

Category:

Documents


0 download

TRANSCRIPT

Appendix B

JAVA GRAPH ADT

This appendix, written by Dr. Michael Dinneen in 1999, presents a simplified abstractclass for representing a graph abstract data type (ADT). Although it is fully functional,it purposely omits most exception handling and other niceties that should be in any com-mercial level package. These details would distract from our overall (introductory) goalof showing how to implement a basic graph class in Java.

Our plan is to have a common data structure that represents both graphs and digraphs.A graph will be a digraph with anti-parallel arcs; that is, if (u; v) 2 E then (v; u) 2 E

also. The initial abstract class presented below requires a core set of methods neededfor the realized graph ADT. It will be extended with the actual internal data structurerepresentation in the form of adjacency matrix or adjacency lists (or whatever the designerpicks).

import java.util.Vector;import java.io.BufferedReader;

/** Current Abstract Data Type interface for (di)graph classes.*/abstract class GraphADT{// Need default constructor, copy and BufferedReader constructors// (commented since java doesn’t allow abstract constructors!)//// public GraphADT();// public GraphADT(GraphADT);// public GraphADT(BufferedReader in);

Right from the beginning we get in trouble since Java does not allow abstract construc-tors. We will leave these as comments and hope the graph class designer will abide bythem. We want to create graphs from an empty graph, copy an existing graph, or read in

233

234 COMPSCI.220FT

one from some external source. In the case of a BufferedReader constructor the userhas to attach one to a string, file or keyboard. We will see examples later.

We now proceed with the graph class interface.

// data structure modifiers//

abstract public void addVertices(int i); // Add some verticesabstract public void removeVertex(int i); // Remove vertexabstract public void addArc(int i, int j); // Add directed edge

public void addEdge(int i, int j) // Add undirected edge{ addArc(i,j); addArc(j,i); }

abstract public void removeArc(int i, int j); // Remove directed edgepublic void removeEdge(int i, int j) // Remove undirected edge{ removeArc(i,j); removeArc(j,i);

}

This small set of methods allows one to build the graph. Notice how we explicitly definethe methods for adding or deleting edges in terms of the two arc methods. An extendedclass can override these to improve efficiency if it wants. We now list a few methods forextracting information from a graph object.

// data structure queries//

abstract public boolean isArc(int i, int j); // Check for arcspublic boolean isEdge(int i, int j) // Check for edges{ return isArc(i,j) && isArc(j,i); }

abstract public int inDegree(int i); // indegreeabstract public int outDegree(int i); // outdegree

public int degree(int i) // Number of neighbours{ return outDegree(i); }

abstract public Vector neighbors(int i); // List of (out-) neighboursabstract public int order(); // Number of verticesabstract public int size(); // Number of edges

Again, we define the isEdge method in terms of the isArc method. We give a defaultdefinition for the degree of a graph in terms of the out-degree. This is fine for graphs butmay not be for digraphs. For our implementation, we want the degree to equal the numberof vertices returned by the neighbors method.

One nice thing to offer is a method to view/save/print a graph. Traditionally in Java wedefine a toString method for this.

COMPSCI.220FT 235

// output (default same as representation)//

abstract public String toString();

// two common external text representations for graphs follow//public String toStringAdjMatrix(){StringBuffer o = new StringBuffer();o.append(order()+"\n");

for( int i = 0; i < order(); i++ ){

for( int j = 0; j < order(); j++ ){

if ( isArc(i,j) ) o.append("1 ");else o.append("0 ");

}o.append("\n");

}return o.toString();}

public String toStringAdjLists(){StringBuffer o = new StringBuffer();o.append(order()+"\n");

for( int i = 0; i < order(); i++ ){

for( int j = 0; j < order(); j++ ){

if ( isArc(i,j) ) o.append(j+" ");}o.append("\n");

}return o.toString();}

} // end of class GraphADT

Notice how we went ahead and defined both the adjacency matrix and adjacency listsoutput methods. We left the toString method as an abstract method for the derivedclasses to define. We want a BufferedReader constructor for a graph class to acceptits own toString output.

To make things convenient for ourselves we require that the first line of our (two) external

236 COMPSCI.220FT

graph representations contain the number of vertices. Strictly speaking, this is not neededfor an 0/1 adjacency matrix. This makes our parsing job easier and this format allows usto store more than one graph per input stream. (We can terminate a stream of graphs witha sentinel graph of order zero.)

B.1 Java adjacency matrix implementation

We now define our first usable graph class based on an adjacency matrix representation(for graphs and digraphs). This class extends our graph interface GraphADT.

import java.io.*;import java.util.*;

/* Current implementation uses adjacency matrix form of a graph.*/public class GraphAdjMatrix extends GraphADT{

// ---------------------------------------------------------------// Internal Representation and Constructors// ---------------------------------------------------------------

private int _space; // Current space allocated.private int _order; // Number of vertices.private boolean _adj[][]; // Adjacency matrix of graph.

private static boolean _allocate(int n)[][] // Free store routines.{return new boolean[n][n];

}

public GraphAdjMatrix() // default constructor{_space = _order = 0;

}

public GraphAdjMatrix(GraphAdjMatrix G) // copy constructor{int n = G.order();if ( n>0 ) _adj = _allocate( n );_space = _order = n;

for( int i = 0; i < n; i++ )for( int j = 0; j < n; j++ )_adj[i][j] = G._adj[i][j];

}

COMPSCI.220FT 237

We isolated a private method1 _allocate that gets memory for the adjacency matrix.The default constructor simply creates an empty graph and thus there is no need to allo-cate any space. The copy constructor simply copies onto a new n-by-n matrix the booleanvalues of the old graph. Notice that we want new storage and not an object reference forthe copy.

We keep an integer variable _space to represent the total space allocated. Whenever wedelete vertices we do not want to reallocate a new matrix but to reshuffle the entries intothe upper sub-matrix. Then whenever adding more vertices we just extend the dimensionof the sub-matrix.

public GraphAdjMatrix(BufferedReader buffer){try {String line=buffer.readLine();StringTokenizer token = new StringTokenizer(line);if ( token.countTokens() != 1 )throw new Error("bad format: number of vertices");

int n = Integer.parseInt(token.nextToken());

if ( n>0 ) _adj = _allocate( n );_order = _space = n;

for( int i = 0; i < n; i++ ){line = buffer.readLine();token = new StringTokenizer(line);

if ( token.countTokens() != n )throw new Error("bad format: adjacency matrix");

for( int j = 0; j < n; j++ ){int entry = Integer.parseInt( token.nextToken() );_adj[i][j] = ( entry != 0 );

}}

} catch (IOException x) {throw new Error("bad input stream");

}}

We have tried to minimize the complexity of this BufferedReader constructor. We

1We use the programming convention of beginning private variables and methods with the underscorecharacter.

238 COMPSCI.220FT

do however throw a couple of errors if something does go wrong. Otherwise, this methodsimply reads in an integer n denoting the dimension of the adjacency matrix and thenreads in the 0/1 matrix. Notice how the use of the StringTokenizer class makes ourtask easy.

We next define several methods for altering this graph data structure.

// ---------------------------------------------------------------// Mutator Methods// ---------------------------------------------------------------

public void addVertices(int n){if ( n > _space - _order ){boolean matrix[][] = _allocate( _order + n );

for( int i = 0; i < _order; i++ ){for( int j = 0; j < _order; j++ )matrix[i][j] = _adj[i][j];

}

_adj = matrix;_space = _order + n;

}

else // expand and reclean matrix{for( int i = 0; i < _order + n; i++ ){for( int j = _order; j < _order + n; j++ )_adj[i][j] = _adj[j][i] = false;

}}_order += n;}

public void removeVertex(int v){_order--;

int i;for( i = 0; i < v; i++ ){

for( int j = v; j < _order; j++ ){

COMPSCI.220FT 239

_adj[i][j] = _adj[i][j + 1];}

}

for( i = v; i < _order; i++ ){int j;for( j = 0; j < v; j++ ){_adj[i][j] = _adj[i + 1][j];

}

for( j = v; j < _order; j++ ){_adj[i][j] = _adj[i + 1][j + 1];

}}

}

These two methods allow the user to add or delete vertices from a graph. If we have al-ready allocated enough space the addVerticesmethod will simply expand the currentsize (while initializing entries to false). Otherwise, a new larger matrix is allocated anda copy is done.

The removeVertex method is somewhat complicated in that we have to remove a rowand column from the matrix corresponding to the vertex being deleted. We decided todo this in two passes. The first pass (for variable i < v) simply shifts all column indicesj � v to the left. The second pass (for variable i � v) has to shift entries up by one whilealso shifting column indices j � v to the left. The user of the graph should realize thatthe indices of the vertices change!

public void addArc(int i, int j){_adj[i][j] = true;

}

public void removeArc(int i, int j){_adj[i][j] = false;

}

Finally, above, we have two relatively trivial methods for adding and deleting arcs (andedges). Recall that the add/remove edge methods of class GraphADT are defined in termsof the arc methods.

The methods to access properties of the graph are also pretty straightforward.

240 COMPSCI.220FT

// ---------------------------------------------------------------// Accessor Methods// ---------------------------------------------------------------

public boolean isArc(int i, int j){

return _adj[i][j];}

public int inDegree(int i){int sz = 0;for( int j = 0; j < _order; j++ ) if ( _adj[j][i] ) sz++;return sz;

}

public int outDegree(int i){int sz = 0;for( int j = 0; j < _order; j++ ) if ( _adj[i][j] ) sz++;return sz;

}

Our constant-time method for checking whether an arc is present in a graph is given abovein the method isArc. Unfortunately, we have to check all neighbors for computing thein- and out- degrees. Also the method, given below, for returning a list of neighbors for avertex will need to scan all potential vertices.

public Vector neighbors(int i){Vector nbrs = new Vector();

for (int j= 0; j<_order; j++){if ( _adj[i][j] ) nbrs.addElement( new Integer(j) );

}

return nbrs;}

public int order() { return _order; }

public int size() // Number of arcs (edges count twice){int sz = 0;

// boolean undirected = true;for( int i = 0; i < _order; i++ )for( int j = 0; j < _order; j++ )

COMPSCI.220FT 241

{if ( _adj[i][j] ) sz++;

// if ( _adj[i][j] != _adj[j][i] ) undirected = false;}return sz; // undirected ? sz / 2 : sz;

}

The order of the graph is stored in an integer variable _order. However, we have tocount all true entries in the boolean adjacency matrix to return the size. Notice that ifwe are working with an undirected graph this returns twice the expected number (since westore each edge as two arcs). If we specialize this class we may want to uncomment theindicated statements to autodetect undirected graphs (whenever the matrix is symmetric).It is probably safer to leave it as it is written, with the understanding that the user knowshow size is defined for this implementation of GraphADT.

// default output is readable by constructor//public String toString() { return toStringAdjMatrix(); }

} // end class GraphAdjMatrix

We finish our implementation by setting our output method toString to return an adja-cency matrix. Recall the method toStringAdjMatrix was defined in the base classGraphADT.

B.2 Java adjacency lists implementation

We now present an alternate implementation of our graph ADT using the adjacency listsdata structure. We will use the Java API class Vector to store these lists.

import java.io.*;import java.util.*;

/* Current implementation uses adjacency lists form of a graph.*/public class GraphAdjLists extends GraphADT{

// ---------------------------------------------------------------// Internal Representation and Constructors// ---------------------------------------------------------------

private Vector _adj; // Vector of Vector of Integers

private void _allocate(int n)

242 COMPSCI.220FT

{_adj = new Vector();for( int i = 0; i < n; i++ ) _adj.addElement(new Vector());

}

public GraphAdjLists() // default constructor{_allocate(0);

}

public GraphAdjLists(GraphAdjLists G) // copy constructor{ int n = G.order();_adj = new Vector();for( int i = 0; i < n; i++ ) _adj.addElement(G.neighbors(i));

}

We mimic the adjacency matrix code here with a private method to allocate memory.The method _allocate creates a list of lists (i.e., a Vector of Vectors). Note thecreated list, using new Vector(), within the argument of the addElement method.The default constructor creates a list of no lists (i.e., no vertices). For better efficiency,the copy constructor takes over the role of our allocator and appends the neighbor lists ofthe graph parameter G directly onto a new adjacency list.2

public GraphAdjLists( BufferedReader buffer ){try {String line = buffer.readLine();StringTokenizer token = new StringTokenizer( line );if ( token.countTokens() != 1 )throw new Error("bad format: number of vertices");

int n = Integer.parseInt( token.nextToken() );_allocate(n);

for( int u = 0; u < n; u++ ){line = buffer.readLine();token = new StringTokenizer( line );while ( token.hasMoreTokens() ){int v = Integer.parseInt( token.nextToken() );((Vector) _adj.elementAt(u)).addElement(new Integer(v));

}}

} catch (IOException x) {throw new Error("bad input stream");

2Yes, this is probably illustrating a bad programming style. Why?

COMPSCI.220FT 243

}}

Our stream constructor reads in an integer denoting the order n of the graph and thenreads in n lines denoting the adjacency lists. Notice that we do not check for correctnessof the data. For example, a graph of 5 vertices could have erroneous adjacency listswith numbers outside the range 0 to 4. We leave these robustness considerations for anextended class to fulfil, if desired. Also note that we do not list the vertex index in frontof the individual lists and we use white space to separate items. A blank line indicates anempty list (i.e., no neighbors) for a vertex.

// ---------------------------------------------------------------// Mutator Methods// ---------------------------------------------------------------

public void addVertices(int n){if ( n > 0 ){for( int i = 0; i < n; i++)_adj.addElement(new Vector());

}}

public void removeVertex(int i){_adj.removeElementAt(i);

Integer I = new Integer(i);for( int u = 0; u < order(); u++ ){Vector uVec = (Vector) _adj.elementAt(u);uVec.removeElement(I); // remove i from

// adjacency lists

for( int v = 0; v < uVec.size(); v++) // relabel larger// indexed nodes

{int nbr = ((Integer) uVec.elementAt(v)).intValue();if ( nbr > i )uVec.setElementAt( new Integer( nbr - 1 ), v );

}}

}

Adding vertices is easy for our adjacency lists representation. Here we just expand theinternal _adj list by appending new empty lists. The removeVertexmethod is a littlecomplicated in that we have to scan each list to remove arcs pointing to the vertex being

244 COMPSCI.220FT

deleted. We also have chosen to relabel vertices so that there are no gaps (i.e., we wantvertex indexed by i to be labeled Integer(i)). A good question would be to find amore efficient removeVertex method. One way would be to also keep an in-neighborlist for each vertex. However, the extra data structure overhead is not desirable for oursimple implementation.

public void addArc( int i, int j ){if ( isArc(i,j) ) return;((Vector) _adj.elementAt(i)).addElement( new Integer(j ));

}

public void removeArc( int i, int j ){((Vector) _adj.elementAt(i)).removeElement( new Integer(j) );

}

Adding and removing arcs is easy since the methods to do this exist in the Vector class.All we have to do is access the appropriate adjacency list. We have decided to place asafe guard in the addArc method to prevent parallel arcs from being added between twovertices.

// -------------------------------------------------------------// Accessor Methods// ---------------------------------------------------------------

public boolean isArc( int i, int j ){

return ((Vector)_adj.elementAt(i)).contains(new Integer(j));}

public int inDegree(int i){int sz = 0;for( int j = 0; j < order(); j++ ) if ( isArc(j,i) ) sz++;return sz;

}

public int outDegree(int i){return ((Vector) _adj.elementAt(i)).size();

}

Note how we assume that the contains method of a Vector object does a data equal-ity check and not just a reference check. The outDegree method probably runs inconstant time since we just return the list’s size. However, the inDegree method has tocheck all adjacency lists and could have to inspect all arcs of the graph/digraph.

COMPSCI.220FT 245

public Vector neighbors( int i ){return (Vector)((Vector) _adj.elementAt(i)).clone();

}

public int order() { return _adj.size(); }

public int size() // Number of arcs (counts edges twice).{int sz = 0;for( int i = 0; i < order(); i++ ){sz += ((Vector) _adj.elementAt(i)).size();

}return sz;}

We do not want to have any internal references to the graph data structure being availableto non-class members. Thus, we elected to return a clone of the adjacency list for ourneighbors method. We did not want to keep redundant data so the order of our graphis simply the size of the _adj vector. We let the Vector class over allocate space, ifneeded, so we do not need a _space variable either.

// default output readable by constructor//public String toString() { return toStringAdjLists(); }

} // end class GraphAdjLists

Again, we have the default output format for this class be compatible with the constructorBufferedReader.

B.3 Standardized Java graph class

We now have two implementations of a graph class as specified by our abstract classGraphADT. We want to write algorithms that can handle either format. Since Javais object-oriented we could have all our algorithms take a GraphADT object and therun-time dynamic mechanism should ascertain the correct adjacency matrix or adjacencylists methods. For example, we could write a graph coloring algorithm prototyped aspublic int color(GraphADT G) and pass it either a GraphAdjMatrix or aGraphAdjLists. And it should work fine!

However, being humans and not liking to do so much typing, we decide to use the simpler(and much more common) name Graph for the graph object in our algorithms. This

246 COMPSCI.220FT

assumes the user has defined a Graph class as follows: 3

// uncomment one of the following lines as the current representation//

public class Graph extends GraphAdjLists//public class Graph extends GraphAdjMatrix{public Graph() { super(); }public Graph(BufferedReader in) { super(in); }public Graph(Graph G) { super(G); }

}

We use the super calls to the base class constructors for a transparent interface. A testprogram for our graph class is given below (for Graph being represented in this programas GraphAdjLists).

public class test {

public static void main(String argv[]){Graph G = new Graph();

G.addVertices(5); G.addArc(0,2); G.addArc(0,3); G.addEdge(1,2);G.addArc(2,3); G.addArc(2,0); G.addArc(2,4); G.addArc(3,2);G.addEdge(4,1); G.addArc(4,2);

System.out.println(G);

G.removeArc(2,0); G.removeArc(4,1); G.removeArc(2,3);

System.out.println(G.toStringAdjMatrix());

G.addVertices(2); G.addArc(5,4); G.addArc(5,2); G.addArc(5,6);G.addArc(2,6); G.addArc(0,6); G.addArc(6,0);

System.out.println(G);

G.removeVertex(4); G.removeEdge(5,0); G.addVertices(1);G.addEdge(1,6);

System.out.println(G.toStringAdjLists());}

} // test

3Alternatively, we could have renamed GraphADT as Graph. However, being somewhat modest, weelected not to establish this as the “standard” graph class.

COMPSCI.220FT 247

The expected output, using JDK version 1.1.6, is given below. Note that the last versionof the digraph G has a vertex of out-degree zero in the adjacency lists. (To compile ourprogram we type ‘javac test.java’ and to execute it we type ‘java test’ at our command-lineprompt ‘$’.)

$ javac test.java$ java test52 32 40 1 3 421 2

50 0 1 1 00 0 1 0 10 1 0 0 10 0 1 0 00 0 1 0 0

72 3 62 41 4 6222 4 60

72 32 61 522 5

1

B.4 Graph algorithms

All of our graph algorithms (public static methods) will be placed in a class called:

public class GraphAlgs{// search algorithms//

248 COMPSCI.220FT

static public int BFS( Graph G, int v, int[] LevelOrder ){ ... }

static public int DFS( Graph G, int v,int[] PreOrder, int[] PostOrder )

{ ... }

// other algorithms added later//// ...

} // class GraphAlgs

Our breadth-first search algorithm will use a queue of Integers to store the most re-cently visited vertices and use the head of this queue to dictate how to explore the graphfurther.

static public int BFS(Graph G, int v, int[] LevelOrder){int n = G.order();for( int i = 0; i < n; i++ ) LevelOrder[i] = 0;

Queue toGrow = new Queue();int cnt = 0;

LevelOrder[v] = ++cnt;toGrow.enque(new Integer(v));

while (!toGrow.empty()){int grow = ((Integer) toGrow.deque()).intValue();

Vector nbrs = G.neighbors(grow);

for( int i = 0; i < nbrs.size(); i++ ){int u = ((Integer) nbrs.elementAt(i)).intValue();if ( LevelOrder[u] == 0 ) // i.e., not visited yet{

LevelOrder[u] = ++cnt;toGrow.enque( new Integer(u) );

}}

}return cnt;

}

Note that we have to write our own class Queue since Java has only a class Stack in

COMPSCI.220FT 249

the API4. At the end of the DFS subsection we will show a test program using this BFSalgorithm (as well as the DFS algorithm) on the two graphs of Example 15.For our DFS algorithm we will write a recursive algorithm to do the backtracking. Tokeep both the pre-order and post-order label of the vertices we define a private inner-classcountPair (of class GraphAlgs). The actual recursive search is also done with a pri-vate method called doDFS, which will hide the existence of the local class countPair.

static private class countPair // private counters for DFS search{int cnt1, cnt2;int inc1() { return ++cnt1; } // increment and return countersint inc2() { return ++cnt2; }

};static privatevoid doDFS(Graph G, int v, int[] PreOrder,

int[] PostOrder, countPair cnt){PreOrder[v] = cnt.inc1();

Vector nbrs = G.neighbors(v);

for( int i = 0; i < nbrs.size(); i++ ){

int u = ((Integer) nbrs.elementAt(i)).intValue();

if ( PreOrder[u] == 0 ) // Have we not seen vertex u before?{doDFS(G, u, PreOrder, PostOrder, cnt);

}}

PostOrder[v] = cnt.inc2();return;

}

static public int DFS(Graph G, int v,int[] PreOrder, int[] PostOrder)

{int n = G.order();for( int i = 0; i < n; i++ ) PreOrder[i] = PostOrder[i] = 0;

// returns number of nodes reached//countPair cnt = new countPair();doDFS(G, v, PreOrder, PostOrder, cnt);

4In Java 2 (JDK 1.2) one would simply use (i.e. extend) the java.util.LinkedList class.

250 COMPSCI.220FT

return PostOrder[v];}

Note that the user has to preallocate the arrays PreOrder and PostOrder before call-ing the DFS algorithm. Recall that array parameter variables are references to the storageallocated in the calling methods. If we allocated new storage in the method DFS then thecalling methods would not be able to access the results.

We give a test search program below that calls both the BFS and DFS algorithms on astream of graphs (either from the keyboard or redirected from a file).

import java.io.*;

public class search {

public static void main(String argv[]){try {

// BufferedReader input = new BufferedReader(new FileReader(argv[0]));BufferedReader input =

new BufferedReader(new InputStreamReader(System.in));

while(true){Graph G = new Graph(input);int n=G.order(); if ( n == 0 ) break;

System.out.print(G.toStringAdjLists());

int preOrder[] = new int[n];int postOrder[] = new int[n];

GraphAlgs.BFS(G,0,preOrder);

System.out.print("BFS (levelorder): ");for( int i=0; i<n; i++) System.out.print(preOrder[i] + " ");System.out.print("\n");

GraphAlgs.DFS(G,0,preOrder,postOrder);

System.out.print("DFS (preorder): ");for( int i=0; i<n; i++) System.out.print(preOrder[i] + " ");System.out.print("\n");

System.out.print("DFS (postorder): ");for( int i=0; i<n; i++) System.out.print(postOrder[i] + " ");

COMPSCI.220FT 251

System.out.print("\n");}

} catch ( Exception e ) { System.err.println("Exception: "+e); }

} // main

} // class search

We run this program on the two graphs of Example 15 below:

$ java search <ex2.lists91 20 2 3 40 1 4 81 5 61 2 7 8334 82 4 7

BFS (levelorder): 1 2 3 4 5 7 8 9 6DFS (preorder): 1 2 3 7 4 8 9 5 6DFS (postorder): 9 8 4 7 3 5 6 2 1

71 2 360 41 5 650 2 4 6

BFS (levelorder): 1 2 3 4 6 7 5DFS (preorder): 1 2 4 7 5 6 3DFS (postorder): 7 2 5 6 4 3 1

Each graph is echoed to the screen then the pre-order labellings of the BFS and DFSsearches are printed, followed by the post-order labels of the DFS search. The reader canverify that these labels are consistent with Examples 16 and 18We can use the following class for converting a digraph to a graph.

252 COMPSCI.220FT

public class uGraph extends Graph{public uGraph() { super(); }

public uGraph(uGraph G) { super(G); }

public uGraph(Graph G){super(G);

int n = order();

// create underlying graph by adding symmetric edges//for( int i=0; i<n-1; i++)for( int j=i+1; j<n; j++){if ( isArc(i,j) ) super.addArc(j,i);elseif ( isArc(j,i) ) super.addArc(i,j);

}}

// public uGraph(BufferedReader) -- use base class directly.

// redefine size for undirected graph

//

public int size() { return super.size()/2; }

// ... also directed edge methods need fixing// (would like to privatize them but Java doesn’t allow it)//public void addArc(int i, int j) { super.addEdge(i,j); }public void removeArc(int i, int j) { super.removeEdge(i,j); }//public boolean isArc(int i, int j) { return super.isArc(i,j); }

}

The constructor from a graph (digraph) makes sure that arcs are provided in both direc-tions. We also redefined some of the edge related methods (e.g., size() returns number ofedges, not the number of arcs).

We now present some connectivity algorithms.

static public boolean isConnected(uGraph G){int[] tmp = new int[G.order()];

COMPSCI.220FT 253

return BFS(G,0,tmp) == G.order();}

static public boolean isConnected(Graph G) // for underlying graph{uGraph G2 = new uGraph(G);return isConnected(G2);

}

static public boolean isStronglyConnected(Graph G){int n = G.order();int[] pre = new int[n];int[] post = new int[n];

if (DFS(G,0,pre,post) < n) return false;

Graph R = new Graph(); // copy of G with all arcs reversedR.addVertices(n);//for( int i = 0; i < n; i++ ){

Vector nbrs = G.neighbors(i);

for( int k = 0; k < nbrs.size(); k++ ){int u = ((Integer) nbrs.elementAt(k)).intValue();R.addArc(u,i);

}}return (DFS(R,0,pre,post) == n);

}

Methods for checking acyclicity now follow.

static public boolean isAcyclic(uGraph G){int n=G.order();int m=G.size();

// for O(n) running time [adjacency lists]if (m >= n) return false;

int[] PreOrder = new int[n];int[] PostOrder = new int[n];countPair cnt = new countPair();

int components = 0;

254 COMPSCI.220FT

//for( int i = 0; i < n; i++ ){if (PreOrder[i] > 0) continue;

// else try next component with root i//doDFS(G, i, PreOrder, PostOrder, cnt);components++;

}return n == m + components;

}

static public boolean isAcyclic(Graph G) // no cycles// (of length > 1)?

{int n=G.order();int[] PreOrder = new int[n];int[] PostOrder = new int[n];boolean[] span = new boolean[n];

for (int i=0; i<n; i++) // check if any vertex is on a cycle{

if ( span[i] ) continue; // try next component

int cnt = DFS(G, i, PreOrder, PostOrder);

for( int j = 0; j < n; j++){if ( PreOrder[j] > 0 ) span[j] = true;

Vector nbrs = G.neighbors(j);

for( int k = 0; k < nbrs.size(); k++ ){int u = ((Integer) nbrs.elementAt(k)).intValue();

if ( PostOrder[u] > PostOrder[j] )return false; // Bingo!

// note: PostOrder[u] > 0 since u is reachable from j}

}

if (cnt == n) break; // all vertices spanned}return true;

}

Acyclic digraphs do not have cycles of length two so this algorithm will return false forany undirected graph with at least one edge.

COMPSCI.220FT 255

We used a boolean array span to make sure that we have investigated all vertices of thegraph. The i loop guarantees that each vertex is in some DFS tree.In the following Java code we decided to illustrate a slightly different programming style.Instead of calling the BFS method directly (like we did above for our isAcyclic algo-rithm with DFS) we modify the BFS code as needed. We also use the Java BitSet classinstead of a boolean array (span) and use Vector and Enumeration classes insteadof our Queue class.

static public int girth(Graph Gin) // returns minimum girth >= 3{

uGraph G = new uGraph(Gin); // compute undirected girthint n = G.order();

int best = n + 1; // girth n+1 if no cycles found

for ( int i = 0; i < n - 2; i++ ){ // look for a cycle from all but last two

BitSet span = new BitSet(n);span.set(i);int depth = 1; // do a BFS search keeping track of depth

Vector distList = new Vector();distList.addElement( new Integer(i) );

while (depth*2 <= best && best > 3){Vector nextList = new Vector();

for( Enumeration e = distList.elements();e.hasMoreElements(); )

{Vector nbrs =G.neighbors(((Integer) e.nextElement()).intValue());

for( int k = 0; k < nbrs.size(); k++ ){int u = ((Integer) nbrs.elementAt(k)).intValue();

if ( !span.get(u) ){span.set(u);nextList.addElement(new Integer(u));

}else // we have found some walk/cycle{// is there a cross edge at this level//

256 COMPSCI.220FT

if ( distList.contains(new Integer(u)) ){best = depth * 2 - 1; break;

}// else even length cycle (as an upper bound)//if ( nextList.contains(new Integer(u)) ){best = depth * 2;

}}

}} // for vertices at current depth

distList = nextList; // next try set of vertices// further away

depth++;}

}return best;

}

Note that we return jV j+ 1 if we do not find any cycles. Since we are originating a BFSfrom all but two of the vertices we do not have to separately worry about ensuring allcomponents have been checked. The line

while (depth*2 <= best && best > 3)

indicates that we stop searching if the depth of the current BFS tree is greater than analready found cycle. We also break out of a grow loop above if we find a small cycle (across edge on the current level).Next is a method to check whether a graph is bipartite.

static public boolean isBipartite( Graph Gin ){uGraph G = new uGraph(Gin); // color underlying graphint n = G.order();

int color[] = new int[n]; // will toggle between 1 and 2

for( int v = 0; v<n; v++ ) // start at first vertex{if ( color[v] > 0 ) continue;color[v] = 1;

Queue toGrow = new Queue(); // use BFS queue searchtoGrow.enque(new Integer(v));

COMPSCI.220FT 257

while ( !toGrow.empty() ){int grow = ((Integer) toGrow.deque()).intValue();

Vector nbrs = G.neighbors(grow);

for( int i = 0; i < nbrs.size(); i++ ){int u = ((Integer) nbrs.elementAt(i)).intValue();

if ( color[u] == 0 ) // not colored yet{color[u] = 3 - color[grow]; // set to other colortoGrow.enque(new Integer(u));

}else // check for different color{if ( color[u] == color[grow] ) return false;

}}

} // more nodes in this component

} // while all components have been checked

return true;}

The only tricky part of the above algorithm is our way of switching between color 1 and2, (3 minus “previous color”). We use the array color to indicate when all vertices (allcomponents) have been colored.

Below we give a test program for the last three algorithms. This time we read in a fileof graphs using the first command-line argument as the filename. After printing out anadjacency lists representation of each graph we decide whether it is acyclic, what theunderlying girth is, and determine whether it is bipartite (2-colorable). The first graph oforder 9 is a tree (but cyclic as viewed as a digraph). Having girth undefined (denoted by10) tells us that it is a tree. The second graph is a DAG but does have an underlying 3cycle (and thus is not bipartite). Our algorithms did correctly find the 4 cycle of the thirdgraph as its girth and concluded that it is 2-colorable (has only even-length cycles).

import java.io.*;

public class cycles {

public static void main(String argv[])

258 COMPSCI.220FT

{try {

BufferedReader input = new BufferedReader(new FileReader(argv[0]));

while(true){Graph G = new Graph(input);int n = G.order(); if ( n == 0 ) break;

System.out.print(G.toStringAdjLists());

if ( GraphAlgs.isAcyclic(G) ){System.out.print("Graph is acyclic, ");

}else{System.out.print("Graph is not acyclic, ");

}

System.out.print("has underlying girth " + GraphAlgs.girth(G));

if ( GraphAlgs.isBipartite(G) ){System.out.println(", and is 2-colourable.");

}else{System.out.println(", and is not 2-colourable.");

}}

} catch ( Exception e ) { System.err.println("Exception: "+e); }

} // main

} // class cycles

A sample run on the three graphs of Example 27 is given below. (The $ line indicateswhat the author typed at his unix command prompt.)

$ java cycles ex3.lists9230 31 2 4 53

COMPSCI.220FT 259

3 6 7 8555

Graph is not acyclic, has underlying girth 10, and is 2-colorable.

71 24 53663 6

Graph is acyclic, has underlying girth 3, and is not 2-colorable.

91 20 4 50 3 52 61 6 71 2 73 4 84 5 86 7

Graph is not acyclic, has underlying girth 4, and is 2-colorable.

Next, some methods for topological sorting.

static public int[] topSort1(Graph G, int firstV){

int n = G.order();int[] PreOrder = new int[n];int[] PostOrder = new int[n];int[] sort = new int[n];

int cnt = DFS(G, firstV, PreOrder, PostOrder);

for( int i = 0; i < n; i++)sort[i] = cnt - PostOrder[i] + 1;

return sort;}

static public int[] topSort2(Graph G)

260 COMPSCI.220FT

{int n = G.order();int[] sort = new int[n];

int[] inDeg = new int[n];for( int i = 0; i < n; i++ ) inDeg[i] = G.inDegree(i);

int cnt = 0;boolean progress = true;//while (progress){progress = false;

for (int v=0; v<n; v++){if ( inDeg[v] == 0 ){sort[v] = ++cnt;progress = true;inDeg[v] = -1;

Vector nbrs = G.neighbors(v);

for( int k = 0; k < nbrs.size(); k++ ){int u = ((Integer) nbrs.elementAt(k)).intValue();inDeg[u] = inDeg[u] - 1;

}}

} // for v

} // while nodes exist with inDegree == 0.

return sort;}

Below we present a test program for computing one topological order for each input graphof a stream of DAGs. We assume that vertex 0 is the first vertex (which is required formethod topSort1).

import java.io.*;public class topsort {

private static void printSort(String label, int[] sort){int n = sort.length;int actual[] = new int[n];

COMPSCI.220FT 261

System.out.print(label);for( int i = 0; i < n; i++ ) actual[sort[i] - 1] = i;for( int i = 0; i < n; i++ )System.out.print(actual[i] + " ");

System.out.println();}

public static void main(String argv[]){try {

BufferedReader input =new BufferedReader(new InputStreamReader(System.in))

while(true){Graph G = new Graph(input);int n = G.order(); if ( n == 0 ) break;

System.out.print( G.toStringAdjLists() );

printSort( "sort1: ", GraphAlgs.topSort1(G,0) );printSort( "sort2: ", GraphAlgs.topSort2(G) );

}

} catch ( Exception e ) {System.err.println("Exception: "+e);

}

} // main

} // class topsort

Notice how we wrote an auxiliary method printSort to display the vertex indices inthe given topological order. The output of this program, when run on the three DAGs ofExample 31, is given next.

$ java topsort <ex4.lists51 23 43 4

sort1: 0 2 1 4 3sort2: 0 1 2 3 4612 34 5

262 COMPSCI.220FT

sort1: 0 1 3 2 5 4sort2: 0 1 2 3 4 571 2 52 3 4 53 4663 6

sort1: 0 1 5 2 4 3 6sort2: 0 1 2 4 5 3 6

Both algorithms do produce valid topological orders for their input. Notice how eachalgorithm gives a different (and correct) result.The following method maxDistance returns the maximum distance of any of the ver-tices or jV j if not all vertices are reachable, along with setting an integer array dist withthe corresponding shortest distances.

static public int maxDistance( Graph G, int v, int dist[] ){int n = G.order();

for (int i=0; i<n; i++) dist[i]=n; // set to maximum distance

dist[v]=0;int depth = 1; // next distance in BFSint cnt = 1; // how many vertices reachable

Vector distList = new Vector();distList.addElement( new Integer(v) );

while( distList.size() > 0 ){

Vector nextList = new Vector();

for( Enumeration e = distList.elements();e.hasMoreElements(); )

{Vector nbrs =G.neighbors(((Integer) e.nextElement()).intValue());

for( int k = 0; k < nbrs.size(); k++ ){int u = ((Integer) nbrs.elementAt(k)).intValue();

COMPSCI.220FT 263

if (dist[u] == n) // first time reachable?{cnt++;dist[u] = depth;nextList.addElement( new Integer(u) );

}}

}distList = nextList; // next try set of vertices

// further awaydepth++;

}return cnt == n ? depth-2 : n;

}

In the above method we did a breadth-first search and kept a counter cnt to indicate ifwe have reached all of the vertices.We can now calculate the diameter of a graph (or digraph) by calling the maxDistancemethod from each node.

static public int diameter( Graph G ){int n = G.order();int max = 0;int [] dist = new int[n];

for( int v = 0; v < n; v++ ){int d = maxDistance( G, v, dist );max = d > max ? d : max;

}return max;

}

public static int[][] distanceMatrix( Graph G ){int n = G.order();int D[][] = new int[n][n];

for( int v = 0; v < n; v++ ){maxDistance( G, v, D[v] );

}return D;

}

Notice how we can pass in a row of the defined distance matrix D to our maxDistancemethod, which accepts an integer array for assigning the path distances.