• Java 2 Platform Standard Edition 5.0 API specification
• OpenJDK source code repository for library classes
• Source code from Data structures and problem-solving using Java, third edition
Graph interfaceWhen last semester's Java class dealt with directed graphs, we didn't have an implementation like Weiss's to work from, so we designed our own. The file /home/stone/courses/java/examples/Graph.java shows what we came up with: a generic interface that allows the vertices of a graph to be objects of any kind. The roster of methods that we came up with differs radically from Weiss's selection, although there is some overlap. The techniques that we'll use to implement this interface also differ from the ones he proposes.
graphs, to
hold the classes that we'll define in this lab.Graph interfaceTo implement this interface, we need a data structure that holds three kinds of information: (a) the vertices; (b) the arcs -- that is, which vertices are connected to which others in the graph; and (c) the arc weight function -- that is, the weight associated with each arc (which Weiss calls the "edge cost").
One approach to choosing a data structure would be to follow the
mathematicians' definition of a directed graph as closely as possible,
using three fields: one being a Set<Vertex> of some kind, to hold
the vertices; a second a Set<Arc<Vertex>>, for the arcs; and the
third a Map<Arc<Vertex>, Double>, for the arc weights, since finite
functions are generally implemented as maps. An Arc<Vertex> could
then be a structure containing fields for the tail and tip vertices of the
arc. Alternatively, we could eliminate the arc-weight map simply by
storing each arc's weight in the arc itself.
Arc class suited to this implementation.DirectedGraph class that implements the Graph interface.
One limitation of the data structure described in the preceding section is
that, in implementing the getSuccessors method, we have to look at
every arc in the graph, adding the ones with a specified tail to our result
set. In a large graph, the amount of computation could be substantial, and
most of the work has no effect on the result. To make matters worse, it
turns out that many common graph operations invoke the getSuccessors
function frequently.
For instance, one common operation is to determine whether there is a
path from a given vertex start to another given vertex
finish. A path is a sequence of arcs in which the tail of the first
arc is start, the tip of the last arc is finish, and the tail
of each arc after the first is the tip of the arc that precedes it. (As a
special case, when start is the same vertex as finish, the
null sequence counts as a path.)
Another formulation of this definition of a path suggests a recursive
approach: There is a path from start to finish if, and only
if, either (a) start is the same vertex as finish or (b)
there is a path from some successor of start to finish.
Graph interface, write a
static method hasPath that takes a Graph<Vertex> and two
vertices as its arguments and returns a boolean indicating whether there is
a path in the graph from the first of the two vertices to the second.getSuccessors could take you around
and around, without ever making progress towards finish. To prevent
this, add an argument to the method that actually performs the recursion --
a Set<Vertex> containing all the vertices that are already on the
path that you're constructing (i.e., all the vertices that have had the
role of start in some recursive call that isn't yet finished). Then
skip over these already-used vertices when considering possible successors
of the current code; it's pointless to revisit them, because doing
so would complete a cycle.
By selecting a different data structure for directed graphs, we can make
the getSelections procedure much faster. The idea is that a
directed graph could be a kind of map, in which the keys are the vertices
of the graph and the values are sets of arcs. Each vertex would be mapped
to the set of arcs that originate at that vertex. (In diagrams like Figure
14.1 of our textbook, these arcs would be the arrows leading away from the
vertex.) To get the successors of a given vertex, we'd simply recover the
corresponding set of arcs and collect their tips.
hasPath method that you wrote in the
preceding section by determining which of the graph's vertices are
connected by paths.