Vous êtes sur la page 1sur 8

Join The Dots

Algorithms
An Introduction To Graphs, Part 2

Alfresco
B efore I continue with my intro-
duction to graphs, one of my
readers was kind enough to point
graph). We provided three
different ways of storing a graph in
memory: as a matrix, a triangular
out that Ive become unreachable. matrix (not for digraphs though),
For the past few articles, Ive or an array of linked lists. Finally,
neglected to add my email address we looked at depth-first traversals.
to the mini-bio at the end of the Phew! After that quick rsum,
article. Oops, sorry about that, it lets move on (of course, if youve by Julian Bucknall
wasnt my intention to become forgotten some of the above,
incommunicado. You can of please quickly go over Octobers Why would we want to do this
course reach me by sending a mes- column and meet us back here). (ie, visit all nodes in a depth-first
sage to julianb@turbopower.com fashion)? There is a neat algorithm
or to our Esteemed Editor, who will Topological Sorts that falls out from such a
forward it if need be. And please do One of the things I didnt really depth-first traversal called a topo-
drop me a line; that includes touch on with my previous discus- logical sort. Suppose we have a set
compliments, brickbats, errors in sion was a depth-first traversal of of tasks that need to be done. Cer-
text or code etc. If your message all the nodes in unconnected tain of these tasks require others
makes a valid point, Ill be sure to graphs. The depth-first traversal to be performed first, in other
include it in a sidebar in a future weve seen so far finds all the words they have prerequisites.
article. nodes that can be visited from a Examples of this are developing a
Anyway, onwards with graphs, given source node. It is entirely computer program (we need to
since we have a lot to cover. You possible that some nodes in the design the database before writing
may recollect that in the October graph would not be visited by such the code, we have to decide on the
1998 instalment of Algorithms a traversal. If this is the case, we data items were going to track
Alfresco, we talked about the graph call the graph unconnected. With a before we design the database, the
structure. We learned that a graph non-directed graph its generally install program comes last of all,
consists of a set of nodes (or verti- simple to see if a graph is after all the EXEs and DLLs are fin-
ces) connected together by edges. unconnected by looking at its pic- ished), devising a recipe for choco-
The edges could be either two way ture: basically the graph falls into late chip cookies (we can only put
(if there was an edge between node two or more sub-graphs with no the cookies in the oven after weve
A and node B then there is an edge edges connecting them. For a mixed the cookie dough), getting
between B and A, the same one) or digraph, in many cases its not dressed (we have to put on our
the edges were directed (if there obvious. If we wanted to visit abso- underwear first, unless we happen
was an edge from A to B then it lutely every node, wed start out to be Superman) and so on. Trans-
does not follow that there is an from the first node and do a lating this into nodes and edges in
edge from B to A, we can think of depth-first traversal on that, then our graph vernacular: a node is a
the edge having an arrowhead wed look for a node that hasnt task, an edge is directed and shows
showing the direction of the edge). been visited and do a depth-first the prerequisite attribute (its from
The latter kind of graph is known as traversal on that. Wed continue node task must be performed
a digraph (or directed graph). Both this process until all the nodes had before its to node task and the
nodes and edges could contain been visited. Listing 1 shows arrow points towards the to node).
data (if an edge contains data it is ExecuteAll, the implementation of A topological sort organizes the
usually known as a weighted this algorithm. tasks in order so that prerequisites
are done before their dependent
tasks. An important restriction
Listing 1: Depth-first ExecuteAll method.
must be made on the graph first: it
procedure TaaDepthFirstIterator.ExecuteAll( must have no cycles; in other
var aHasCycle : boolean; aExtraData : pointer);
var words, it must be a directed acy-
i : integer; clic graph, also known as a dag.
ithHasCycle : boolean;
begin Whats a cycle? Well a cycle is
aHasCycle := false;
for i := 0 to pred(dfiGraph.NodeCount) do begin when you start from a node, visit
if (PitrCounter(dfiNodes[i])^.cMarker = 0) then begin
Execute(i, ithHasCycle, aExtraData);
nodes along directed edges, and
aHasCycle := aHasCycle or ithHasCycle; get right back where you started
end;
end; from (see Figure 1). If you think in
end;
terms of tasks and prerequisites,

26 The Delphi Magazine Issue 41


reaches itself. A cycle in other nodes (or tasks) are meant to be
a b words. Notice that in an undirected
graph, cycles abound: there is
executed in left to right order.
Notice that when we draw in all of
always an edge from a child to a the edges from the original dag
parent since its the same edge as they all point from left to right. If
from the parent to the child! But, you think about it, thats a graphic
were only talking about directed visualization of prerequisites.
graphs here. The Execute method
c from the previous article has been Breadth-First Traversals
enhanced to report whether a Another way of walking through a
cycle was found. This months disk graph is known as breadth-first tra-
Figure 1:
has the new code. versal. With depth-first traversal
A cycle in a directed graph.
So how do we do a topological we tried to go as far as we could
sort on a dag? Its this simple: from the start node until we hit a
this means that if the graph of the create a linked list, execute a dead end and then we backtracked
tasks is drawn up and contains a depth-first traversal, and in the until we found another unvisited
cycle, a node could be its own postprocess event for each node, edge to follow. This traversal used
prerequisite. And we cant have add the node to the front of the a stack to aid in the backtracking
that! linked list. After the traversal is (actually, if you look back at the
Making sure that a digraph is over, the linked list represents the depth-first iterator I wrote in the
acyclic is easy. We just have to do a tasks organized into prerequisite previous article, youll notice that
depth-first traversal and make sure order. And thats it. Listing 2 shows there isnt an explicit stack, I just
that the edges we follow never end a topological sort. What weve used recursion and hence was
on a node that is preprocessed. done here is, rather than return a implicitly using the program stack
Hows that? Think about it. A linked list with the nodes in topo- instead).
depth-first traversal works like logical order, weve created an Well, with breadth-first travers-
this: mark the current node as event and the event will be fired for als, we visit every neighboring
preprocessed, go along each edge the nodes in sequence. This in turn node to the start node first, and
from this node and recursively do means that we dont have to define then visit every neighboring node
the same with each node at the a linked list external to the to those nodes and so on. Its a
end. After weve followed all the depth-first iterator, the implemen- kind of ripple effect, moving
edges from the current node, mark tor of the event handler can do outwards from the original node. It
the node postprocessed. Suppose what he likes, hell just get them in turns out that breadth-first
we do manage to reach a the right order. traversals use a queue to store the
preprocessed node: this would Once we have a topologically
mean that the node is one of our sorted dag we can redraw the
Figure 2:
predecessors since a node is only graph as shown in Figure 2. The
Topographically sorting a dag.
marked postprocessed once all of
its children and grandchildren, etc, 00
1
are visited and marked as 9 2
postprocessed. Hence we will have
discovered a path from a node, 8 3 5 7 2 4 1 3 0 9 8 6
through a child node, through a
grandchild node, and so on, that 7 4
6 5
Listing 2: Topographical sort.
procedure TaaDepthFirstIterator.TopologicalSort( while (Head <> nil) do begin
aExtraData : pointer); // process the head node
var if Assigned(dfiProcessSortedNode) then
TSList : PbfiListItem; dfiProcessSortedNode(
Head, Temp : PbfiListItem; Self, Head^.liIndex, aExtraData);
HasCycle : boolean; // move down the list, dispose of the old head node
begin Temp := Head;
// create the linked list Head := Head^.liNext;
New(TSList); Dispose(Temp);
TSList^.liNext := nil; end;
try except
// now execute the depth first traversal on all the // on error clean up the remainder of the linked list
// nodes: this will add all the nodes to our linked list while (Head <> nil) do begin
Reset; Temp := Head;
ExecuteAll(HasCycle, TSList); Head := Head^.liNext;
// now trigger the event handler, cleaning up the linked Dispose(Temp);
// list as we go end;
Head := TSList^.liNext; raise;
try end;
// if there was a cycle, the topo sort is meaningless finally
if HasCycle then Dispose(TSList);
raise Exception.Create('digraph is not acyclic'); end;
// walk the linked list end;

January 1999 The Delphi Magazine 27


procedure TaaBreadthFirstIterator.Execute( cMarker := 2;
aFromIndex : integer; aExtraData : pointer); OurLevel := cLevel;
var end;
i : integer; // iterate through the edges from this node, push
NewNodeInx : integer; // unvisited nodes onto the queue
Edge : longint; i := 0;
OurLevel : integer; while bfiGraph.GetNodeEdge(
OurIndex : integer; OurIndex, i, Edge, NewNodeInx) do begin
begin with PitrCounter(bfiNodes[NewNodeInx])^ do begin
// perform preprocessing on the node if (cMarker = 0) then begin
if Assigned(bfiPreProcess) then // update process information
bfiPreProcess(Self, aFromIndex, aExtraData); cParent := OurIndex;
// mark the node as preprocessed cLevel := succ(OurLevel);
with PitrCounter(bfiNodes[aFromIndex])^ do begin // perform preprocessing on the node
cMarker := 1; if Assigned(bfiPreProcess) then
end; bfiPreProcess(Self, NewNodeInx, aExtraData);
// push the node onto the queue // mark the node as preprocessed
bfiEnqueue(aFromIndex); cMarker := 1;
// whilst there are still items in the queue... // push the node onto the queue
while not bfiQueueIsEmpty do begin bfiEnqueue(NewNodeInx);
// pop the next item off the queue end;
OurIndex := bfiDequeue; end;
// perform postprocessing on the node inc(i);
if Assigned(bfiPostProcess) then end;
bfiPostProcess(Self, OurIndex, aExtraData); end;
// mark the node as postprocessed end;
with PitrCounter(bfiNodes[OurIndex])^ do begin

Listing 3:
post-process when the node is iterator does, we can easily print
The Execute method for the
actually visited and the edges out the shortest path by walking
breadth-first iterator class.
from it are then followed. backwards following these back-
ward links. We can use recursion
nodes weve visited, instead of a Shortest Path or an explicit stack to print out the
stack. Another thing to notice about the path in the correct order (every
Again, just like in the depth-first breadth-first traversal is that it time we move backwards from a
traversal case, well define a sepa- gives you the shortest path from given node we push the parent
rate class to perform a breadth- the source node to every other node onto a stack until we reach
first traversal. This provides isola- node. By shortest path, I mean the the source node; now we pop the
tion of the iterator from the class number of edges that have to be nodes off the stack and print them
used to store the graph and hence followed to get from the source out to give the shortest path).
enables a breadth-first iterator node to a given node (well be Listing 4 gives the details where we
object to work with any graph seeing another definition of short- are using the program stack
object descended from our original est path for a weighted graph in a implicitly through recursion.
graph class. The design is a little minute). We can see this in a Notice how we first traverse the
more tricky than before because not-very-rigorous fashion by whole graph from the given index,
we have to code up a queue as well, visualizing the breadth-first search and then we print out the shortest
but its pretty easy to follow. Like as proceeding in waves or rip- path to the given index by walking
before, well have a pre-process ples from the source nodes. First backwards (since nodes are
and a post-process event so that all the immediate neighbor nodes untyped we require an extra rou-
you have control over when you are visited (their shortest paths tine to print a node: this routine
want to do some work with a node are all of length 1), the first wave. could print the name or identifier
being visited. The interface for the Then all their neighbors are vis- of the node, for example). It could
breadth-first iterator is roughly the ited, the second wave, and their very well be that there is no path
same as for the depth-first iterator shortest path distances are of from the source node to the target
from last time, and the code for the length 2. And so on, so forth, node (in which case well hit a
Execute method is shown in Listing rippling outwards. dead end on a node without a
3. Figure 3 shows the steps taken in In fact, if we make note of the predecessor in our backwards
a breadth-first traversal for a parent (or predecessor) node of walk), so we ought to report that
sample digraph (its the one in each node as we traverse the fact as well. The implementation
Figure 2 of Octobers article, so you graph, which youll notice that the makes the shortest path routine a
can compare the depth-first and
breadth-first traversals).
Figure 3: Breadth-first traversal, step-by-step.
If you look at Listing 3, youll see
that the pre-process event gets A A A A B A B
fired just as the node gets added to
D E D E D E D C
the queue, and the post-process
event when the node is popped off
A B A B
the queue. In other words,
pre-process is when the node is E D C E D C
first seen from the vantage point
of its predecessor node, and G F G

28 The Delphi Magazine Issue 41


function TaaBreadthFirstIterator.bfiShortPathPrim( Result := true;
aFromIndex : integer; aToIndex : integer; end else
aExtraData : pointer) : boolean; Result := false;
var end;
Parent : integer; end;
begin end;
if (aFromIndex = aToIndex) then begin function TaaBreadthFirstIterator.ShortestPath(
{ we reached source node, print it & return success } aFromIndex : integer; aToIndex : integer;
if Assigned(bfiPrintNode) then aExtraData : pointer) : boolean;
bfiPrintNode(Self, aToIndex, aExtraData); begin
Result := true; // first execute the breadth first traversal: this sets up
end else begin // our internal data structure
Parent := PitrCounter(bfiNodes[aToIndex])^.cParent; Reset;
if (Parent = -1) then begin Execute(aFromIndex, nil);
{we've hit a dead end-no path back to the source node} // now traverse from the ToIndex node back to the
Result := false; // FromIndex node pushing visited nodes on the stack:
end else begin // we'll then unwind the stack to print the shortest path
{recurse to the parent, if successful print this node} Result :=
if bfiShortPathPrim(aFromIndex, Parent, aExtraData) bfiShortPathPrim(aFromIndex, aToIndex, aExtraData);
then begin end;
if Assigned(bfiPrintNode) then
bfiPrintNode(Self, aToIndex, aExtraData);

Listing 4:
and the priority queue class from first. The main traversal method
The unweighted shortest path.
Novembers Algorithms Alfresco. for solving weighted graph
Before we start we must make a algorithms is priority-first. The
function: it returns True if a path couple of important assumptions. previous two used a stack and a
was found, False if not. In my original graph class I allowed queue respectively; the priority-
All right! Time for a breather. Up an edge to be weighted with a first traversal uses a priority
to this point weve discussed (in typeless pointer. Since our code up queue (seems obvious, no?). The
depth) graph representations, to now hasnt had to worry about traversal works by taking the edge
depth-first traversals and breadth- what data structure these typeless with the largest priority (however
first traversals. We really ought to pointers point to, we havent wor- that may be defined). Note that I
move onto some heavyweight ried about it too much. Well, now is didnt say cost or weight, but prior-
algorithms, otherwise our the time to worry about it. For the ity. Well see a couple of different
Esteemed Editor will be wondering algorithms that follow we need to ways of calculating the priority in a
whether Im losing touch! lay down a couple of rules. Rule 1 is moment for different algorithms.
that we need to be able to take two What happens in a priority-first
Weighted Graph Algorithms edge weights and state whether traversal is this:
Lets now consider weighted the first is less than, equal to, or 1. Mark the starting node as
graphs. If you recall, a weighted greater than the second. The preprocessed.
graph is a graph where each edge weights have to be sortable, in 2. Compute the priority of the
has a weight or a cost associated other words. Rule 2 is even more starting node.
with it. An obvious example is a strict: given two edge weights, we 3. Insert it into the priority
map where the cities and towns are need to be able to add them queue.
the nodes and the roads between together. In other words we need 4. Remove the node with the
them are the edges of a graph. If we to calculate the total cost of going maximum priority from the queue,
denote the cost of an edge as from node X to node Y and then on mark it as postprocessed. Follow
being the average time it would to node Z. These two rules pretty each edge from that node.
take to travel along that road, we well force us to assume that the 5. If an edge leads to a node that
can then start asking questions like weight of an edge is numeric. We hasnt been visited yet, mark the
which journey between city X and shall assume that edge weights are node as preprocessed, calculate
city Y would take the smallest time of type longint from now on (the its priority and insert it into the pri-
and through which other cities code on the disk has been changed ority queue.
must we pass? Or, which tour that to reflect this). 6. If the edge leads to a
starts and ends at city X and visits The next assumption is less preprocessed node, the node is in
each and every city once would strict. One of the algorithms we the priority queue already, so cal-
take the shortest time? shall meet in a minute (Dijkstras culate the new priority of the node
If, instead, the cost of an edge algorithm) only works if the (it might have changed because of
was the distance along the road, weights are never negative. Well the different path weve taken),
and we wished to lay our high see why in a moment, but for now find the node in the priority queue
bandwidth high cost optical cable well embrace this assumption as and change the priority if the new
to connect primary hubs in all of well. value is larger than the old. Update
the cities, along which roads So, edge weights are non- the priority queue, if so.
should we lay it to minimize the negative longint values. 7. If the priority queue is not
amount of cable? empty, return to step 4.
These types of problems are Priority-First Traversals If you think back to Novembers
solvable by using weighted graphs, The two traversals weve seen so Algorithms Alfresco column for a
a couple of important algorithms far are depth-first and breadth- moment, I deliberately introduced

30 The Delphi Magazine Issue 41


a priority queue towards the end of sum the cost of the edges in the the smaller the cost to get to the
the article that allows step 6 to be various spanning trees, then the node, the larger the priority of the
performed efficiently. Foresight or minimum spanning tree is the one node. We could use a formula like
what?! whose total cost is the smallest. VeryLargeValue minus EdgeCost to
Anyway, before I get too Before we look at how to imple- calculate the priority. However, a
self-congratulatory, it seems Im ment Prims algorithm with a cleverer plan would be to change
forgetting something important: priority-first traversal, and hence the priority queue to act on the
what on earth is the priority value? define how to calculate the prior- edge cost directly and return the
Answer: it depends on which algo- ity, lets see how it works. smallest (that is, to use a
rithm were talking about. Before Suppose we have the graph in min-heap). In fact this is what we
you start thinking that your author Figure 4. We want to find the mini- will do: we will change the priority
has finally become way too eso- mum spanning tree starting at queue to a min-heap and, in step 5
teric and academic and self- node a (or, in the vernacular, above, well replace the priority of
referential and maybe its time to rooted at a). Visit a. Select the edge an already preprocessed node if
read One Last Compile, lets look at with the smallest cost going from a the new value is smaller than the
the first weighted graph algorithm. and visit the node at the end. This old.
This is the one to determine a mini- node is b and the cost was 5 units. We can now take all of this and
mum spanning tree (or, How to Visit Now select the edge with the small- produce a priority-first traversal
All the Nodes and Minimize the est cost going from either a or b, iterator, and then a specialized
Cost). This is known as Prims and visit the node at the end. In our routine for calculating the mini-
Algorithm. In our couple of exam- case this is the edge from b to d mum spanning tree. The Execute
ples a few paragraphs back, this with cost 10. Repeat the process. method of the iterator is shown in
will be the solution to the optical Selecting the next edge reveals a Listing 5. Note that when we store
fibre one. choice of two: from a to e, or from d the node in the priority queue, we
to a. We reject the second alterna- actually store the internal counter
Prims Algorithm tive because a has already been record (the one that takes account
A spanning tree is a representation visited, so we visit e. Again, select- of a nodes parent, its priority and
of the graph as a multi-way tree, ing the next edge gives us a choice so on). This enables the priority
with every node in the graph some- of two: d to e, and b to c. We reject queue to compare priorities of
where in the tree (ie, we make the the first choice (e has been visited items (or rather, enables us to
assumption that the graph is before) so we use the second and easily write a comparison function
connected), and the links between visit c. At this point we can stop for the priority queue). Im sure
parents and children in the tree are since weve visited all the nodes. you can follow the code (supple-
edges in the graph. There can be Figure 5 shows the steps to pro- mented by the files on the
several spanning trees for a given duce the final minimum spanning diskette) and compare it to the
graph, it all depends where we tree. seven-step algorithm above.
start and which edges we travel So, to return to our priority-first So how do we code Prims algo-
down first. A minimum spanning traversal, what is the priority value rithm using this iterator? Really
tree is the one of these many span- of a node? Well, I hope that you can simply as Listing 6 shows. Notice
ning trees such that the cost of tra- see that its inversely proportional the routine to evaluate the priority
versing the tree is minimal for the to the edge cost to visit that node: based on Prims Algorithm: all we
given graph. In other words, if we
Figure 5: The minimum spanning tree for the
Figure 4: A digraph. digraph in Figure 4.

5 5
a b a b
15 20 15 20
15 10 15 10
e c e c

20 d 25 20 d 25

January 1999 The Delphi Magazine 31


procedure TaaPriorityFirstIterator.Execute( NewNodeInx) do begin
aEvalPriority : TaaEvalPriority; aFromIndex : integer; with PitrCounter(pfiNodes[NewNodeInx])^ do begin
aExtraData : pointer); {totally unvisited before}
var if (cMarker = 0) then begin
i : integer; // update process information
NewNodeInx : integer; cParent := OurIndex;
Edge : longint; cLevel := succ(OurLevel);
OurLevel : integer; // perform preprocessing on the node
OurIndex : integer; if Assigned(pfiPreProcess) then
NewPriority: longint; pfiPreProcess(Self, NewNodeInx, aExtraData);
begin // mark the node as preprocessed
// perform preprocessing on the node cMarker := 1;
if Assigned(pfiPreProcess) then // calculate the priority
pfiPreProcess(Self, aFromIndex, aExtraData); cPriority := aEvalPriority(Self, OurIndex, Edge,
// mark the node as preprocessed NewNodeInx);
with PitrCounter(pfiNodes[aFromIndex])^ do begin // push the node onto the queue
cMarker := 1; cHandle := pfiQueue.Add(pfiNodes[NewNodeInx]);
cPriority := 0; end else if (cMarker = 1) then begin
// push the node onto the queue {already preprocessed}
cHandle := pfiQueue.Add(pfiNodes[aFromIndex]); // calculate the new priority
end; NewPriority := aEvalPriority(Self, OurIndex, Edge,
// whilst there are still items in the queue... NewNodeInx);
while (pfiQueue.Count <> 0) do begin // if it is less than the current one, update the
// pop the next item off the queue // node and reheapify the queue
OurIndex := PitrCounter(pfiQueue.Remove)^.cIndex; if (NewPriority < cPriority) then begin
// perform postprocessing on the node cParent := OurIndex;
if Assigned(pfiPostProcess) then cLevel := succ(OurLevel);
pfiPostProcess(Self, OurIndex, aExtraData); cPriority := NewPriority;
// mark the node as postprocessed pfiQueue.Replace(cHandle, pfiNodes[NewNodeInx]);
with PitrCounter(pfiNodes[OurIndex])^ do begin end;
cMarker := 2; end;
OurLevel := cLevel; end;
end; inc(i);
// iterate through the edges from this node, push end;
// unvisited nodes onto the queue end;
i := 0; end;
while pfiGraph.GetNodeEdge(OurIndex, i, Edge,

Listing 5:
function EvalPrimsPriority(aSender : TObject; aFromIndex : integer;
The Execute method for the aEdgeCost : longint; aToIndex : integer) : longint;
priority-first iterator class. begin
Result := aEdgeCost;
end;
procedure MinSpanningTree(aGraph : TaaGraph; aProcessNode : TaaProcessNode;
do is to return the cost of the edge aExtraData : pointer);
var
leading to the node. Iter : TaaPriorityFirstIterator;
begin
Iter := TaaPriorityFirstIterator.Create(aGraph);
Iter.OnPostProcess := aProcessNode;
Travelling Salesmen Problems try
What is so special about Prims Iter.Execute(EvalPrimsPriority, 0, aExtraData);
finally
algorithm and minimum spanning Iter.Free;
end;
trees? Well, theyre used in travell- end;
ing salesmen type problems.
Suppose that a salesman has a
Listing 6: Prim's algorithm.
set of stores that he must visit in Believe it or not, solving this
order to demonstrate and sell his type of problem generically is diffi-
companys latest widget. It makes cult in the extreme. Actually, lets the minimum. Suppose now that
sense for him to minimize the rephrase that. Although the solu- there are 10 different stores to
amount of time it takes him to tion is simple to state (list all the visit. This time hed have to find
travel between these stores so that possible tours and select the one the minimal tour in a set of 10! or
he can take the rest of the day off with the smallest cost), the prob- 3,628,800 possibilities. This is get-
(and indeed to make sure that he lem that arises is the number of ting a little unwieldy, but if his boss
has some time to take off!). Using possible tours increases exponen- had given him a PC that could cal-
his knowledge of the traffic in his tially with the number of nodes and culate the cost of 100,000 tours a
city, where these stores are, the edges. In our salesman problem, second itd take about 40 seconds
one-way systems, etc, he can draw suppose that there were 5 possible to find the minimum one. If there
up a graph with the stores as nodes stores and that he could go from were 15 stores then there would be
and the cost of the edge between any store to any other. He starts off 1,307,674,368,000 different tours in
two nodes the time it would take to at the factory, has a choice of 5 all, which would take about 151
go from one store to the other. It is stores to visit first, then a choice of days to calculate the minimal one
assumed that he can get from any 4 stores to visit next, and so on on his little PC. This is just not
store to any other without passing until hes visited the last store and feasible.
through a third (the graph repre- then returns to the factory. There The travelling salesman prob-
senting the map of the stores and are 5! different possible tours he lem is one of a set of NP-complete
roads is said to be complete). How could make, and thats 120. Calcu- problems (where NP means non-
does he calculate a tour of the lating the cost of each tour is pretty deterministic polynomial-time).
stores that will take the minimum simple and it wouldnt take too The main characteristic of all
time? long to calculate them all and find these problems is that the running

32 The Delphi Magazine Issue 41


time of an algorithm to solve the we can get a pretty good minimal target). Suppose our graph repre-
problem increases exponentially tour from a minimum spanning sents an airline network between a
with the number of items in the tree. Since Prims algorithm for set of cities. Each link (or edge)
problem. Compare this with obtaining the minimum spanning between two cities represents a
sequential search, for example. tree is O(E * logn), where E is the flight, and the cost of the flight is
This simple algorithm for finding number of edges and n the number the time taken to get from one city
an item in an array is linear in terms of nodes, we can obtain a pretty to the other. How can we calculate
of execution speed: the time to find good answer in much, much less the minimal time it would take to
a item in 1,000 of them will take time than getting the correct one. go from city A to city B?
about 10 times longer than finding (In fact, without going into the Enter Dijkstras Algorithm. Like
one in 100 of them. Another exam- mathematics, we can derive a tour Prims Algorithm, this uses a prior-
ple: the binary search algorithms from a minimum spanning tree ity-first traversal. Actually
execution time increases propor- that costs not more than twice the Dijkstras Algorithm gives us the
tionally to the log of the number of cost of the minimal tour and in shortest path from the source
items, so if it takes 5 units of time to practice, much less.) The deriva- node to every other node (note
find an item in 32 of them, itll take tion of a tour from a minimum that in this section by shortest path
10 units of time to find an item in spanning tree will have to wait for we mean the path with the overall
1,024 of them (ie, twice as long for another time, though. lowest cost), and well deal with the
the square of the number of items). shortest path between two given
When faced with an NP-complete Dijkstras Algorithm nodes in a moment. The algorithm
problem, the plan is to try and find Now lets move on to our second works like this. We walk the graph
a reasonably good approximation algorithm with weighted graphs: one node at a time, starting from
to the solution in a reasonable finding the path with the lowest the start node. As we visit each
amount of time. In other words, we cost between two nodes in a graph, node, we calculate the minimum
acknowledge the intractability of also known as Dijkstras path from the start to that node.
finding the real solution and Algorithm. This is an incremental procedure
instead settle for a pretty good Often we need to calculate the as well see, we dont actually try
answer that we can calculate rea- smallest cost to move from one and determine the shortest path
sonably quickly. In the travelling node (the start) in a graph to from first principles every time. So
salesman problem, it turns out that another (the destination or which nodes do we choose to visit?
Look at Figure 6. This a weighted visit any of the unvisited nodes c, d, 5
digraph and we are going to find e at this point, and we choose the a b
out the shortest path from node a edge that is least costly: the one to 50
to node e. Remember that by d. We visit d and then update the DV 15 40
shortest path I dont mean the array again. This time itll equal [0, 10
path with the smallest number of 5, 25, 15, 35]; notice how weve
e 5 c
hops, for that would be [a, e] and gotten closer to both c and e by
this section of the article would be this point. We still havent visited e
very short. No, I mean the path yet and so we continue. The near- 20 d 10
with the smallest cost. For exam- est unvisited node this time is c so
ple, the cost of the path [a, e] is 50 we take that edge, and update DV
Figure 6:
units; can we do better? again to [0, 5, 25, 15, 30]. The
Work out the minimum
Dijkstras Algorithm goes like nearest unvisited node is now e
cost to go from a to e.
this. Associate a value for each and so we finally visit it. The cost
node to represent the cost of get- of getting to e from a is 30 units,
ting there from node a. Lets store and if wed saved the shortest does. We do the same trick as we
this in an array called DV for paths at each step wed have did with the shortest path algo-
Dijkstras value (or maybe dis- noticed that we took the path [a, b, rithm with the breadth-first tra-
tance value). We initialize the d, c, e] to get there. versal (ie, work out the path from A
array: DV[a] is zero, DV[b] is 5, DV[e] This then is Dijkstras Algorithm. to B by working backwards from B,
is 50 and the values for nodes c and It can be shown that this greedy pushing the parent nodes onto a
d are unknown as yet and so we set algorithm does produce the short- stack until we get to A, and then
them to a large number. (I know we est path from start to target node. popping them off in order).
can just look at the graph and work In fact, if we let it visit every single
it out, but were trying to illustrate node in the graph we can find out All Done For Now
an algorithm here!) We choose the the shortest path from a given After all that, take a huge pat on
next node to visit by selecting a node to every other. (Notice that your back. Weve just gone
node that can be visited from the the DV array in our example stores through some pretty chunky con-
ones we already have visited, and the cost of going from a to every cepts, structures, algorithms and
we select the one with the smallest other node since we managed to coding. Its taken three articles to
Dijkstras value. Well, from the ini- visit every node in the process.) do it as well, so if youve managed
tial setup, we have only visited Although our example doesnt to stay with me through it all, well
node a (its where we are starting show this, it is entirely possible for done. This has been a learning
from after all), and we can only the algorithm to hit a dead end and exercise for me as well, although I
visit b or e. We choose b since it has have to backtrack. It doesnt vaguely knew what graphs were
the smallest value. We now update matter though: if the graph is con- and had read about the various
our DV array. From b we can visit nected we will eventually get to the algorithms weve covered, Id
either c or d. The distance value for target node. never tried to code them before.
c, going through b, would be 45 (ie, Lets cast this in terms of our pri- If you want to know more about
5 + 40). If this is less than the cur- ority-first traversal. Simple, right? graphs, I used three books as
rent value in DV[c] then we replace Well, yes. The priority of a node is research: Introduction to Algo-
it (it is, and so we do). The same now its total distance from the rithms by Cormen, Leiserson and
goes for d. At this point the array DV start node (or rather the total cost Rivest; Practical Algorithms in C++
contains [0, 5, 45, 15, 50]. Start of getting to the node from the start by Bryan Flamig; and Data Struc-
the process over again. We can node). All we have to do is alter the tures, Algorithms, and Performance
priority evaluation function and by Derick Wood.
were done. Which is what Listing 7 Next time, well take it a little
Listing 7: Dijkstra's Algorithm.
easier. Promise [Thanks! Ed].
function EvalDijkstrasPriority(aSender : TObject; aFromIndex : integer;
aEdgeCost : longint; aToIndex : integer) : longint;
begin
with (aSender as TaaPriorityFirstIterator) do Julian Bucknall can be pretty
Result := GetPriority(aFromIndex) + aEdgeCost;
end; graphic, if a little edgy. He can
procedure SmallestCostPath(aGraph : TaaGraph; aFromIndex : integer; aToIndex : be contacted via email at
integer; aProcessNode : TaaProcessNode; aExtraData : pointer);
var julianb@turbopower.com. The
Iter : TaaPriorityFirstIterator;
begin code that accompanies this article
Iter := TaaPriorityFirstIterator.Create(aGraph); is freeware and can be used as-is
Iter.OnPrintNode := aProcessNode;
try in your own applications.
Iter.Execute(EvalDijkstrasPriority, aFromIndex, nil);
Iter.TracePath(aFromIndex, aToIndex, aExtraData);
finally
Iter.Free;
1999 Julian M Bucknall
end;
end;

34 The Delphi Magazine Issue 41

Vous aimerez peut-être aussi