It may be helpful to think of arcs as forming an abstract data type in which each value comprises a ``from'' vertex and a ``to'' vertex, with the following operations:
create
Inputs: from-vertex and to-vertex, both
vertices.
Output: result, an arc.
Preconditions: none.
Postcondition: result is directed from
from-vertex to to-vertex.
source
Input: operand, an arc.
Output: result, a vertex.
Preconditions: none.
Postcondition: operand is directed from result to
some vertex.
target
Input: operand, an arc.
Output: result, a vertex.
Preconditions: none.
Postcondition: operand is directed from some vertex to
result.
Vertices and Arcs modules in HP PascalEqualVertex and
PrecedesVertex are defined in and exported from the
Vertices module.
In addition, since the vertices may be of an opaque type accessed through a
pointer, we shall also require an operation that assigns a value of the
Vertex type to a variable of that type by copying the entire
structure, and an operation that deallocates any dynamically allocated
storage associated with such a value. The implementation of these
operations is trivial if the Vertex type does not involve
pointers. Here's how the module looks when the vertices are simply integer
values:
{ This module defines a Vertex data type so that other modules related to
graphs can import it.
Programmer: John Stone, Grinnell College.
Original version: December 3, 1996.
}
module Vertices;
export
type
Vertex = Integer;
function EqualVertex (LeftOperand, RightOperand: Vertex): Boolean;
function PrecedesVertex (LeftOperand, RightOperand: Vertex): Boolean;
procedure AssignVertex (var Target: Vertex; Source: Vertex);
procedure DeallocateVertex (var Operand: Vertex);
implement
function EqualVertex (LeftOperand, RightOperand: Vertex): Boolean;
begin
EqualVertex := LeftOperand = RightOperand
end;
function PrecedesVertex (LeftOperand, RightOperand: Vertex): Boolean;
begin
PrecedesVertex := LeftOperand < RightOperand
end;
procedure AssignVertex (var Target: Vertex; Source: Vertex);
begin
Target := Source
end;
procedure DeallocateVertex (var Operand: Vertex);
begin
end;
end.
Since arcs too are going to be members of sets within a directed graph, the
same kinds of complications must be added to the elegant simplicity of the
abstract data type defined above. But there are no real surprises in the
coding of the Arcs module, either; an arc is basically an
immutable record with two fields, both of type Vertex:
{ This module defines an Arc data type, so that other modules related to
graphs can import it.
Programmer: John Stone, Grinnell College.
Original version: December 3, 1996.
}
$heap_dispose on$
module Arcs;
$search 'vertices.o'$
import
Vertices;
export
type
Arc = ^ArcRecord;
function CreateArc (FromVertex, ToVertex: Vertex): Arc;
function SourceOfArc (Operand: Arc): Vertex;
function TargetOfArc (Operand: Arc): Vertex;
function EqualArc (LeftOperand, RightOperand: Arc): Boolean;
function PrecedesArc (LeftOperand, RightOperand: Arc): Boolean;
procedure AssignArc (var Target: Arc; Source: Arc);
procedure DeallocateArc (var Operand: Arc);
implement
import
StdErr;
const
FirstExceptionCode = 1;
UndefinedArcException = 1;
ExceptionException = 2;
LastExceptionCode = 11;
type
ArcRecord = record
From: Vertex;
Into: Vertex
end;
procedure ArcExceptionHandler (ExceptionCode: Integer);
begin
if (ExceptionCode < FirstExceptionCode) or
(LastExceptionCode < ExceptionCode) then
ExceptionCode := ExceptionException;
WriteLn (StdErr, 'Exception #', ExceptionCode : 1, ' in module Arcs:');
case ExceptionCode of
UndefinedArcException:
WriteLn (StdErr, 'An uninitialized or undefined Arc value was ',
'passed to a procedure or function.');
ExceptionException:
WriteLn (StdErr, 'The ArcExceptionHandler procedure received ',
'an unknown exception code.')
end
end;
function CreateArc (FromVertex, ToVertex: Vertex): Arc;
var
Result: Arc;
begin
New (Result);
AssignVertex (Result^.From, FromVertex);
AssignVertex (Result^.Into, ToVertex);
CreateArc := Result
end;
function SourceOfArc (Operand: Arc): Vertex;
var
Result: Vertex;
begin
Assert (Operand <> nil, UndefinedArcException, ArcExceptionHandler);
AssignVertex (Result, Operand^.From);
SourceOfArc := Result
end;
function TargetOfArc (Operand: Arc): Vertex;
var
Result: Vertex;
begin
Assert (Operand <> nil, UndefinedArcException, ArcExceptionHandler);
AssignVertex (Result, Operand^.Into);
TargetOfArc := Result
end;
function EqualArc (LeftOperand, RightOperand: Arc): Boolean;
begin
Assert ((LeftOperand <> nil) and (RightOperand <> nil),
UndefinedArcException, ArcExceptionHandler);
EqualArc := EqualVertex (LeftOperand^.From, RightOperand^.From) and
EqualVertex (LeftOperand^.Into, RightOperand^.Into)
end;
function PrecedesArc (LeftOperand, RightOperand: Arc): Boolean;
begin
Assert ((LeftOperand <> nil) and (RightOperand <> nil),
UndefinedArcException, ArcExceptionHandler);
if PrecedesVertex (LeftOperand^.From, RightOperand^.From) then
PrecedesArc := True
else if PrecedesVertex (RightOperand^.From, LeftOperand^.From) then
PrecedesArc := False
else
PrecedesArc := PrecedesVertex (LeftOperand^.Into, RightOperand^.Into)
end;
procedure AssignArc (var Target: Arc; Source: Arc);
begin
Assert (Source <> nil, UndefinedArcException, ArcExceptionHandler);
New (Target);
Target^ := Source^
end;
procedure DeallocateArc (var Operand: Arc);
begin
Assert (Operand <> nil, UndefinedArcException, ArcExceptionHandler);
DeallocateVertex (Operand^.From);
DeallocateVertex (Operand^.Into);
Dispose (Operand);
Operand := nil
end;
end.
create
Inputs: vertices, a set of vertices, and arcs, a
set of arcs.
Output: result, a directed graph.
Preconditions: Both of the endpoints of every arc in arcs are
members of vertices.
Postconditions: vertices is the vertex set of
result and arcs the arc set of
result.
vertices
Input: operand, a directed graph.
Output: result, a set of vertices.
Preconditions: none.
Postcondition: operand is the vertex set of
result.
arcs
Input: operand, a directed graph.
Output: result, a set of arcs.
Preconditions: none.
Postcondition: operand is the arc set of
result.
The textbook defines or hints at a number of operations on directed graphs, but it turns out that the most natural implementation of each one involves performing set operations on the constituent vertex and arc sets.
The implementation in HP Pascal requires the importation of two
nearly-identical set modules, one for sets of vertices and the other for
sets of arcs. Each is derived by minor editing from the module presented
in the handout on sets; the major changes are (1)
that the type, procedure, and function names have been changed in obvious
ways to prevent name collisions when both modules are imported; (2) that
set members are now explicitly deallocated during the set-deallocation
procedure; and (3) that assignments to variables of the element types are
now performed by invoking the appropriate Assign procedure,
rather than by the := operation.
Note that the CreateDirectedGraphs procedure begins with an
assertion that checks the correctness of the precondition.
Here's the DirectedGraphs module:
{ This module defines the interface for a DirectedGraph data type and
implements it for HP 9000 Series 700 workstations under HP-UX 9.x, using
HP Pascal.
Programmer: John Stone, Grinnell College.
Original version: November 26, 1996.
Last revised: December 3, 1996.
}
$heap_dispose on$
module DirectedGraphs;
$search 'arc-sets.o, vertex-sets.o, arcs.o, vertices.o'$
import
ArcSets, VertexSets;
export
type
DirectedGraph = ^DirectedGraphRecord;
function CreateDirectedGraph (Vertices: VertexSet; Arcs: ArcSet):
DirectedGraph;
function VerticesOfDirectedGraph (Operand: DirectedGraph): VertexSet;
function ArcsOfDirectedGraph (Operand: DirectedGraph): ArcSet;
procedure AssignDirectedGraph (var Target: DirectedGraph;
Source: DirectedGraph);
procedure DeallocateDirectedGraph (var Operand: DirectedGraph);
implement
import
Arcs, Vertices, StdErr;
const
FirstExceptionCode = 1;
CreateDirectedGraphException = 1;
UndefinedDirectedGraphException = 2;
ExceptionException = 3;
LastExceptionCode = 3;
type
DirectedGraphRecord = record
V: VertexSet;
E: ArcSet
end;
procedure DirectedGraphExceptionHandler (ExceptionCode: Integer);
begin
if (ExceptionCode < FirstExceptionCode) or
(LastExceptionCode < ExceptionCode) then
ExceptionCode := ExceptionException;
WriteLn (StdErr, 'Exception #', ExceptionCode : 1,
' in module DirectedGraphs:');
case ExceptionCode of
CreateDirectedGraphException:
WriteLn (StdErr, 'The CreateDirectedGraph function received an ',
'arc set relating vertices not in the proposed vertex set.');
UndefinedDirectedGraphException:
WriteLn (StdErr, 'An uninitialized or undefined DirectedGraph ',
'value was passed to a procedure or function.');
ExceptionException:
WriteLn (StdErr, 'The DirectedGraphExceptionHandler procedure ',
'received an unknown exception code.')
end
end;
function ArcsRelateVertices (Vertices: VertexSet; Arcs: ArcSet): Boolean;
function BothEndsInVertices (A: Arc): Boolean;
var
Start, Finish: Vertex;
begin
Start := SourceOfArc (A);
if MemberOfVertexSet (Start, Vertices) then begin
Finish := TargetOfArc (A);
BothEndsInVertices := MemberOfVertexSet (Finish, Vertices);
DeallocateVertex (Finish)
end
else
BothEndsInVertices := False;
DeallocateVertex (Start)
end;
begin { function ArcsRelateVertices }
ArcsRelateVertices := EveryMemberOfArcSet (Arcs, BothEndsInVertices)
end;
function CreateDirectedGraph (Vertices: VertexSet; Arcs: ArcSet):
DirectedGraph;
var
Result: DirectedGraph;
begin
Assert (ArcsRelateVertices (Vertices, Arcs),
CreateDirectedGraphException, DirectedGraphExceptionHandler);
New (Result);
AssignVertexSet (Result^.V, Vertices);
AssignArcSet (Result^.E, Arcs);
CreateDirectedGraph := Result
end;
function VerticesOfDirectedGraph (Operand: DirectedGraph): VertexSet;
var
Result: VertexSet;
begin
Assert (Operand <> nil, UndefinedDirectedGraphException,
DirectedGraphExceptionHandler);
AssignVertexSet (Result, Operand^.V);
VerticesOfDirectedGraph := Result
end;
function ArcsOfDirectedGraph (Operand: DirectedGraph): ArcSet;
var
Result: ArcSet;
begin
Assert (Operand <> nil, UndefinedDirectedGraphException,
DirectedGraphExceptionHandler);
AssignArcSet (Result, Operand^.E);
ArcsOfDirectedGraph := Result
end;
procedure AssignDirectedGraph (var Target: DirectedGraph;
Source: DirectedGraph);
begin
Assert (Source <> nil, UndefinedDirectedGraphException,
DirectedGraphExceptionHandler);
New (Target);
AssignVertexSet (Target^.V, Source^.V);
AssignArcSet (Target^.E, Source^.E)
end;
procedure DeallocateDirectedGraph (var Operand: DirectedGraph);
begin
Assert (Operand <> nil, UndefinedDirectedGraphException,
DirectedGraphExceptionHandler);
DeallocateVertexSet (Operand^.V);
DeallocateArcSet (Operand^.E);
Dispose (Operand);
Operand := nil
end;
end.
It is straightforward to give algorithmic equivalents of many of the
abstract notions defined in chapter 9 of Walker's textbook, as well as many
others. Let's start by defining a function that finds the
successors of a given vertex in a directed graph -- the vertices
entered by arcs that leave the given vertex:
function Successors (G: DirectedGraph; Start: Vertex): VertexSet;
var
V: VertexSet;
E: ArcSet;
{ the components of G }
Result: VertexSet;
{ the set of successors of Start, as it is constructed }
procedure AddFinishOppositeStartToResult (A: Arc);
var
AStart, AFinish: Vertex;
{ the end points of arc A }
begin
AStart := SourceOfArc (A);
if EqualVertex (Astart, Start) then begin
AFinish := TargetOfArc (A);
ReplaceVertexSet (Result, AdjoinToVertexSet (Result, AFinish));
DeallocateVertex (AFinish)
end;
DeallocateVertex (AStart)
end;
begin { function Successors }
V := VerticesOfDirectedGraph (G);
{ Assert (MemberOfVertexSet (Start, V)); }
E := ArcsOfDirectedGraph (G);
Result := TheEmptyVertexSet;
ApplyToEachMemberOfArcSet (E, AddFinishOppositeStartToResult);
Successors := Result
end;
(The ReplaceVertexSet procedure simply deallocates the
existing storage occupied by its first argument and then copies a pointer
to its second argument into that first argument; it's used when one wants
to overwrite the old value of a VertexSet variable with a new
one, recycling the old value first.)
If one starts with an entire set Starters of vertices, rather
than a single vertex, and constructs the set of successors of members of
Starters, the result is called the image of
Starters (in the directed graph). Here's a function that
computes the image of a given set of vertices in a given directed graph:
function Image (G: DirectedGraph; Starters: VertexSet): VertexSet;
var
V: VertexSet;
E: ArcSet;
{ the components of G }
Result: VertexSet;
{ the set of successors of Starters, as it is constructed }
procedure AddSuccessorsToResult (Start: Vertex);
var
Additions: VertexSet;
{ the successors of Start }
begin
Additions := Successors (G, Start);
ReplaceVertexSet (Result, UnionOfVertexSets (Result, Additions));
DeallocateVertexSet (Additions)
end;
begin { function Image }
V := VerticesOfDirectedGraph (G);
{ Assert (SubsetOfVertexSet (Starters, V)); }
E := ArcsOfDirectedGraph (G);
Result := TheEmptyVertexSet;
ApplyToEachMemberOfVertexSet (Starters, AddSuccessorsToResult);
Image := Result
end;
The first of the graph-theory notions that Walker presents is that of a
path from one vertex to another. Here is a function that determines
whether there is a (directed) path from one given vertex to another in a
given directed graph. Although it's not entirely obvious from its form,
this function conducts a breadth-first search of the graph, starting from
the Start vertex, until it either finds the
Finish vertex (and returns True or cannot extend
the partial paths it constructs to any new vertices (in which case it
returns False).
function PathExists (G: DirectedGraph; Start, Finish: Vertex): Boolean;
var
Previous: VertexSet;
{ the set of vertices that have already been considered as points
along a path }
Accessible: VertexSet;
{ the successors of vertices that might still constitute forwards
progress towards the goal }
Found: Boolean;
{ indicates whether a path from Start to Finish has yet been
detected }
Newcomers: VertexSet;
{ the newly accessible vertices, not yet in Previous }
begin
Previous := CreateSingletonVertexSet (Start);
Accessible := Successors (G, Start);
Found := MemberOfVertexSet (Finish, Accessible);
Newcomers := RelativeComplementOfVertexSets (Accessible, Previous);
while not Found and not EmptyVertexSet (Newcomers) do begin
ReplaceVertexSet (Previous, UnionOfVertexSets (Previous, Accessible));
ReplaceVertexSet (Accessible, Image (G, Newcomers));
Found := MemberOfVertexSet (Finish, Accessible);
ReplaceVertexSet (Newcomers,
RelativeComplementOfVertexSets (Accessible,
Previous))
end;
PathExists := Found;
DeallocateVertexSet (Previous);
DeallocateVertexSet (Accessible);
DeallocateVertexSet (Newcomers)
end;
It's straightforward to write functions to compute the complete graph on
any set of vertices and the totally unconnected graph on any set of
vertices:
function CompleteGraph (V: VertexSet): DirectedGraph;
var
E: ArcSet;
{ the set of all arcs connecting distinct vertices in V }
procedure AddAllOutgoingArcsToE (Start: Vertex);
procedure AdjoinArcToE (Finish: Vertex);
var
A: Arc;
{ the arc from Start to Finish }
begin
if not EqualVertex (Start, Finish) then begin
A := CreateArc (Start, Finish);
ReplaceArcSet (E, AdjoinToArcSet (E, A));
DeallocateArc (A)
end
end;
begin { function AddAllOutgoingArcsToE }
ApplyToEachMemberOfVertexSet (V, AdjoinArcToE)
end;
begin { function CompleteGraph }
E := TheEmptyArcSet;
ApplyToEachMemberOfVertexSet (V, AddAllOutgoingArcsToE);
CompleteGraph := CreateDirectedGraph (V, E);
DeallocateArcSet (E)
end;
function TotallyUnconnectedGraph (V: VertexSet): DirectedGraph;
var
E: ArcSet;
{ the set of all arcs connecting distinct vertices in V }
begin
E := TheEmptyArcSet;
TotallyUnconnectedGraph := CreateDirectedGraph (V, E);
DeallocateArcSet (E)
end;
One graph qualifies as a subgraph of another if its vertex set is a subset
of the other's and its arc set is a subset of the other's:
function Subgraph (LeftOperand, RightOperand: DirectedGraph): Boolean;
var
LeftVertices, RightVertices: VertexSet;
{ the vertex sets of the left and right operands }
LeftArcs, RightArcs: ArcSet;
{ the arc sets of the left and right operands }
begin
LeftVertices := VerticesOfDirectedGraph (LeftOperand);
RightVertices := VerticesOfDirectedGraph (RightOperand);
if VertexSubset (LeftVertices, RightVertices) then begin
LeftArcs := ArcsOfDirectedGraph (LeftOperand);
RightArcs := ArcsOfDirectedGraph (RightOperand);
Subgraph := ArcSubset (LeftArcs, RightArcs);
DeallocateArcSet (LeftArcs);
DeallocateArcSet (RightArcs)
end
else
Subgraph := False;
DeallocateVertexSet (LeftVertices);
DeallocateVertexSet (RightVertices)
end;
Walker suggests two methods of traversing the part of a graph that is
reachable from a given vertex, so as to visit each vertex in that part of
the graph once, presumably to perform some operation on it. One method is
a ``breadth-first'' traversal, in which the vertices are processed in order
of their distance from the starting vertex; the other is a ``depth-first''
traversal, in which each path is followed out until it either ends or
cycles around to a previously visited vertex. Here is an implementation of
each. Note that the breadth-first traversal uses a queue of vertices to
keep track of those that have been visited but not yet considered for
successors, while the depth-first traversal, written recursively,
implicitly uses a stack:
procedure BreadthFirstTraversal (G: DirectedGraph; Start: Vertex;
procedure Applicand (V: Vertex));
var
Visited: VertexSet;
{ the vertices that have already been visited }
Holding: VertexQueue;
{ unexamined successors of visited vertices }
Selected: Vertex;
{ one vertex at a time, from Holding }
NextBatch: VertexSet;
{ the successors of the selected vertex }
procedure EnqueueInHolding (Vert: Vertex);
begin
Enqueue (Vert, Holding)
end;
begin { procedure BreadthFirstTraversal }
{ V := VerticesOfDirectedGraph (G);
Assert (MemberOfVertexSet (Start, V));
DeallocateVertexSet (V); }
Visited := TheEmptyVertexSet;
Holding := CreateVertexQueue;
Enqueue (Start, Holding);
while not EmptyVertexQueue (Holding) do begin
Selected := Dequeue (Holding);
if not MemberOfVertexSet (Selected, Visited) then begin
Applicand (Selected);
ReplaceVertexSet (Visited, AdjoinToVertexSet (Visited, Selected));
NextBatch := Successors (G, Selected);
ApplyToEachMemberOfVertexSet (NextBatch, EnqueueInHolding);
DeallocateVertexSet (NextBatch)
end;
DeallocateVertex (Selected)
end
end;
procedure DepthFirstTraversal (G: DirectedGraph; Start: Vertex;
procedure Applicand (V: Vertex));
var
Visited: VertexSet;
{ the vertices that have already been visited }
procedure DepthFirstHelper (Selected: Vertex);
var
NextBatch: VertexSet;
{ the successors of the selected vertex }
begin
if not MemberOfVertexSet (Selected, Visited) then begin
Applicand (Selected);
ReplaceVertexSet (Visited, AdjoinToVertexSet (Visited, Selected));
NextBatch := Successors (G, Selected);
ApplyToEachMemberOfVertexSet (NextBatch, DepthFirstHelper);
DeallocateVertexSet (NextBatch)
end
end;
begin { procedure DepthFirstTraversal }
{ V := VerticesOfDirectedGraph (G);
Assert (MemberOfVertexSet (Start, V));
DeallocateVertexSet (V); }
Visited := TheEmptyVertexSet;
DepthFirstHelper (Start);
DeallocateVertexSet (Visited)
end;
Many other operations on directed graphs can be coded elegantly in this
implementation. Here are a few of my favorites:Union and intersection are meaningful operations on directed graphs, and there is also a plausible notion of a complement: The complement of a given graph as the same vertex set, but its arc set contains exactly those ways of connecting vertices that are not members of the original graph's arc set.
function UnionOfDirectedGraphs (LeftOperand, RightOperand:
DirectedGraph): DirectedGraph;
var
LeftVertices, RightVertices: VertexSet;
{ the vertex sets of the left and right operands }
LeftArcs, RightArcs: ArcSet;
{ the arc sets of the left and right operands }
V: VertexSet;
E: ArcSet;
{ the vertex and arc sets of the result }
begin
LeftVertices := VerticesOfDirectedGraph (LeftOperand);
RightVertices := VerticesOfDirectedGraph (RightOperand);
V := UnionOfVertexSets (LeftVertices, RightVertices);
DeallocateVertexSet (LeftVertices);
DeallocateVertexSet (RightVertices);
LeftArcs := ArcsOfDirectedGraph (LeftOperand);
RightArcs := ArcsOfDirectedGraph (RightOperand);
E := UnionOfArcSets (LeftArcs, RightArcs);
DeallocateArcSet (LeftArcs);
DeallocateArcSet (RightArcs);
UnionOfDirectedGraphs := CreateDirectedGraph (V, E);
DeallocateVertexSet (V);
DeallocateArcSet (E)
end;
function IntersectionOfDirectedGraphs (LeftOperand, RightOperand:
DirectedGraph): DirectedGraph;
var
LeftVertices, RightVertices: VertexSet;
{ the vertex sets of the left and right operands }
LeftArcs, RightArcs: ArcSet;
{ the arc sets of the left and right operands }
V: VertexSet;
E: ArcSet;
{ the vertex and arc sets of the result }
begin
LeftVertices := VerticesOfDirectedGraph (LeftOperand);
RightVertices := VerticesOfDirectedGraph (RightOperand);
V := IntersectionOfVertexSets (LeftVertices, RightVertices);
DeallocateVertexSet (LeftVertices);
DeallocateVertexSet (RightVertices);
LeftArcs := ArcsOfDirectedGraph (LeftOperand);
RightArcs := ArcsOfDirectedGraph (RightOperand);
E := IntersectionOfArcSets (LeftArcs, RightArcs);
DeallocateArcSet (LeftArcs);
DeallocateArcSet (RightArcs);
IntersectionOfDirectedGraphs := CreateDirectedGraph (V, E);
DeallocateVertexSet (V);
DeallocateArcSet (E)
end;
function ComplementOfDirectedGraph (Operand: DirectedGraph):
DirectedGraph;
var
OperandArcs: ArcSet;
{ the arc set of Operand }
V: VertexSet;
E: ArcSet;
{ the vertex and arc sets of the result }
procedure ConditionallyAdjoinOutgoingArcs (Start: Vertex);
procedure ConditionallyAdjoinArc (Finish: Vertex);
var
A: Arc;
begin
A := CreateArc (Start, Finish);
if not MemberOfArcSet (A, OperandArcs) then
ReplaceArcSet (E, AdjoinToArcSet (E, A));
DeallocateArc (A)
end;
begin { procedure ConditionallyAdjoinOutgoingArcs }
ApplyToEachMemberOfVertexSet (V, ConditionallyAdjoinArc)
end;
begin { function ComplementOfDirectedGraph }
V := VerticesOfDirectedGraph (Operand);
OperandArcs := ArcsOfDirectedGraph (Operand);
E := TheEmptyArcSet;
ApplyToEachMemberOfVertexSet (V, ConditionallyAdjoinOutgoingArcs);
ComplementOfDirectedGraph := CreateDirectedGraph (V, E);
DeallocateVertexSet (V);
DeallocateArcSet (E)
end;
The converse of a directed graph has the same vertex set, but in
the converse every arc of the original graph is reversed, so that it points
in the other direction. An arc from vertex u to vertex v in
the original graph corresponds to an arc from vertex v to vertex
u in the converse.
function ConverseOfDirectedGraph (Operand: DirectedGraph): DirectedGraph;
var
OperandArcs: ArcSet;
{ the arc set of Operand }
V: VertexSet;
E: ArcSet;
{ the vertex and arc sets of the result }
procedure AdjoinReversedArc (A: Arc);
var
Start, Finish: Vertex;
{ the endpoints of A }
AReversed: Arc;
begin
Start := SourceOfArc (A);
Finish := TargetOfArc (A);
AReversed := CreateArc (Finish, Start);
DeallocateVertex (Start);
DeallocateVertex (Finish);
ReplaceArcSet (E, AdjoinToArcSet (E, AReversed));
DeallocateArc (AReversed)
end;
begin { function ConverseOfDirectedGraph }
V := VerticesOfDirectedGraph (Operand);
OperandArcs := ArcsOfDirectedGraph (Operand);
E := TheEmptyArcSet;
ApplyToEachMemberOfArcSet (OperandArcs, AdjoinReversedArc);
ConverseOfDirectedGraph := CreateDirectedGraph (V, E);
DeallocateVertexSet (V);
DeallocateArcSet (E)
end;
A directed graph is symmetric -- in effect, it is an undirected
graph -- if it is a subgraph of its own converse. (Any two vertices of a
symmetric graph are either connected by an arc in each direction or not
connected at all. In either case, taking the converse gives you a result
indistinguishable from the original.)
function SymmetricDirectedGraph (Operand: DirectedGraph): Boolean; var Converse: DirectedGraph; begin Converse := ConverseOfDirectedGraph (Operand); SymmetricDirectedGraph := Subgraph (Operand, Converse); DeallocateDirectedGraph (Converse) end;The identity graph for a given set of vertices represents the relationship of identity: each vertex bears this relation to itself, but to no other vertex. So the graph consists of nothing but loops -- for each vertex, there is an arc from that vertex to itself.
function IdentityDirectedGraph (V: VertexSet): DirectedGraph;
var
E: ArcSet;
procedure AdjoinLoopToE (Vert: Vertex);
var
Loop: Arc;
begin
Loop := CreateArc (Vert, Vert);
ReplaceArcSet (E, AdjoinToArcSet (E, Loop));
DeallocateArc (Loop)
end;
begin { function IdentityDirectedGraph }
E := TheEmptyArcSet;
ApplyToEachMemberOfVertexSet (V, AdjoinLoopToE);
IdentityDirectedGraph := CreateDirectedGraph (V, E);
DeallocateArcSet (E)
end;
A graph is said to be reflexive if the identity graph of its
vertex set is a subgraph of it (that is, if there is a loop at every vertex
in it):
function ReflexiveDirectedGraph (Operand: DirectedGraph): Boolean;
var
V: VertexSet;
{ the vertex set of Operand }
Id: DirectedGraph;
{ the identity graph on V }
begin
V := VerticesOfDirectedGraph (Operand);
Id := IdentityDirectedGraph (V);
DeallocateVertexSet (V);
ReflexiveDirectedGraph := Subgraph (Id, Operand);
DeallocateDirectedGraph (Id)
end;
The relational product of two directed graphs expresses a sort of
compound relation; there is an arc from vertex u to vertex v
in the relational product of graphs G and H if there is some
vertex w, common to both graphs, such that there is an arc from
u to w in G and an arc from w to v in
H. Suppose, for instance, that the vertices in G and
H are people -- all the people who have ever lived, for instance --
and that in G there is an arc from each person to each of his or her
parents, while in H there is an arc from each perons to each of his
or her sisters (if any). In the relational product of G and
H, there would be an arc from each person to each of his or her
aunts. (And in the relational product of G with itself, there would
be an arc from each person to each of his or her grandparents.)
function RelationalProductOfDirectedGraphs (LeftOperand, RightOperand:
DirectedGraph): DirectedGraph;
var
LeftVertices, RightVertices: VertexSet;
{ the vertex sets of the left and right operands }
V: VertexSet;
E: ArcSet;
{ the vertex and arc sets of the result }
procedure AdjoinImagesOfSuccessors (Start: Vertex);
var
Media: VertexSet;
{ the successors of Start in LeftOperand, from which those that
are not vertices of RightOperand are taken away }
Correlates: VertexSet;
{ the image of those successors -- the vertices to which an arc
from Start must run in the relational product }
procedure AdjoinToE (Finish: Vertex);
var
A: Arc;
begin
A := CreateArc (Start, Finish);
ReplaceArcSet (E, AdjoinToArcSet (E, A));
DeallocateArc (A)
end;
begin { procedure AdjoinImagesOfSuccessors }
Media := Successors (LeftOperand, Start);
ReplaceVertexSet (Media,
IntersectionOfVertexSets (Media, RightVertices));
Correlates := Image (RightOperand, Media);
DeallocateVertexSet (Media);
ApplyToEachMemberOfVertexSet (Correlates, AdjoinToE);
DeallocateVertexSet (Correlates)
end;
begin { function RelationalProductOfDirectedGraphs }
LeftVertices := VerticesOfDirectedGraph (LeftOperand);
RightVertices := VerticesOfDirectedGraph (RightOperand);
V := UnionOfVertexSets (LeftVertices, RightVertices);
E := TheEmptyArcSet;
ApplyToEachMemberOfVertexSet (LeftVertices, AdjoinImagesOfSuccessors);
DeallocateVertexSet (LeftVertices);
DeallocateVertexSet (RightVertices);
RelationalProductOfDirectedGraphs := CreateDirectedGraph (V, E);
DeallocateVertexSet (V);
DeallocateArcSet (E)
end;
A graph is transitive if its relational product with itself is a
subgraph of itself (so that whenever it contains an arc from u to
v and an arc from v to w, it also contains an arc
directly from u to w):
function TransitiveDirectedGraph (Operand: DirectedGraph): Boolean; var Square: DirectedGraph; begin Square := RelationalProductOfDirectedGraphs (Operand, Operand); TransitiveDirectedGraph := Subgraph (Square, Operand); DeallocateDirectedGraph (Square) end;A graph is antisymmetric if no two distinct vertices are connected by arcs each way, or, equivalently, if the intersection of the graph and its converse is a subgraph of the identity graph on its vertices:
function AntisymmetricDirectedGraph (Operand: DirectedGraph): Boolean;
var
Converse: DirectedGraph;
IntersectionWithConverse: DirectedGraph;
OperandVertices: VertexSet;
Id: DirectedGraph;
begin
Converse := ConverseOfDirectedGraph (Operand);
IntersectionWithConverse :=
IntersectionOfDirectedGraphs (Operand, Converse);
DeallocateDirectedGraph (Converse);
OperandVertices := VerticesOfDirectedGraph (Operand);
Id := IdentityDirectedGraph (OperandVertices);
DeallocateVertexSet (OperandVertices);
AntisymmetricDirectedGraph := Subgraph (IntersectionWithConverse, Id);
DeallocateDirectedGraph (IntersectionWithConverse);
DeallocateDirectedGraph (Id)
end;
A graph is a partial ordering on its vertex set if it is
reflexive, antisymmetric, and transitive:
function PartialOrdering (Operand: DirectedGraph): Boolean;
begin
if not ReflexiveDirectedGraph (Operand) then
PartialOrdering := False
else if not AntisymmetricDirectedGraph (Operand) then
PartialOrdering := False
else
PartialOrdering := TransitiveDirectedGraph (Operand)
end;
The difference between a partial and a total ordering is that in a partial
ordering there may be some pairs of distinct vertices that are not
connected by arcs, whereas in a total ordering there are none -- the
complete graph on the vertex set is a subgraph of the union of the graph
and its converse.
function TotalOrdering (Operand: DirectedGraph): Boolean;
var
Converse: DirectedGraph;
UnionWithconverse: DirectedGraph;
OperandVertices: VertexSet;
Complete: DirectedGraph;
begin
if not PartialOrdering (Operand) then
TotalOrdering := False
else begin
Converse := ConverseOfDirectedGraph (Operand);
UnionWithConverse := UnionOfDirectedGraphs (Operand, Converse);
DeallocateDirectedGraph (Converse);
OperandVertices := VerticesOfDirectedGraph (Operand);
Complete := CompleteGraph (OperandVertices);
DeallocateVertexSet (OperandVertices);
TotalOrdering := Subgraph (Complete, UnionWithConverse);
DeallocateDirectedGraph (UnionWithConverse);
DeallocateDirectedGraph (Complete)
end
end;
The relationship expressed by a graph is an equivalence if the
graph is reflexive, symmetric, and transitive. An equivalence divides the
vertex set of the graph into one or more subsets in such a way that the
members of each subset are all related to one another, but no vertices in
different subsets are related at all; these subsets are called the
equivalence classes induced by the relation.
function Equivalence (Operand: DirectedGraph): Boolean;
begin
if not ReflexiveDirectedGraph (Operand) then
Equivalence := False
else if not SymmetricDirectedGraph (Operand) then
Equivalence := False
else
Equivalence := TransitiveDirectedGraph (Operand)
end;
It would, of course, be possible to design a module in which all of these
operations were defined and exported.