Path Finding in Graphs: An Example

Refer to pages 217-219 of Bratko (3rd edition). The sample material
can also be found in the 2nd edition.

 I. Path Finding 

Let G be a graph, and A and Z two nodes in G. We are interested in
a relation:

      path(A, Z, G, P)

where P is an acyclic path from A to Z in G.  

First, let us fix some representations. Assume we represent a graph by 

       graph(Nodes, Edges)

where graph is a function symbol and Nodes is a list of nodes and Edges 
is a list of directed edges in the graph. E.g.

       graph([a,b,c], [c(a,b),c(a,c),c(b,c),c(c,b)])

is a graph with three nodes and four edges where c(W,V) 
means an edge from W to V.

Then, we represent a path by a list of nodes; e.g., 
a path [a,b,c] means that the path starts 
at a and ends at c, and the path is from a to b and then to c.

An acyclic path is one in which no node appears more than once.

The relation path can be defined in terms of another relation

      path1(A, Path1, G, Path)

where A is a node, G is a graph, Path1 is a path in G, and 
Path is an acyclic path in G that goes from A to the beginning of 
Path1 and continues along Path1 up to its end.

The idea is to build Path recursively through 
graduately completing Path1
until its start node is A. 

The program can be written in Prolog as:

path(A, Z, Graph, Path) :- 
          path1(A, [Z], Graph, Path).

path1(A, [A|Path1], Graph, [A|Path1]).

path1(A, [Y|Path1], graph(Nodes, Edges), Path) :-
          member(c(X,Y), Edges),
          not_member(X, Path1),
          path1(A, [X,Y|Path1], graph(Nodes, Edges), Path).

not_member(X, []).
not_member(X, [Y|L]) :-
          not_member(X, L),
          X \== Y. 

member(X, [X|L]).
member(X, [Y|L]) :- member(X, L).


To understand what this program does, try to run it, e.g, with the 
following query:

?- path(a,d,

 II. Hamiltonian Path

A classical problem on graph theory is that of finding 
a Hamiltonian path; i.e., an acyclic path comprising all the nodes in 
the graph.  To solve this problem, we add clauses

hamiltonian(graph(Nodes, Edges), Path) :- 
          path(A, Z, graph(Nodes, Edges), Path),
          eq_set(Path, Nodes).

eq_set(P, N) :- 

subset([], S).
subset([A|L], S) :- 
         member(A, S),
         subset(L, S).

 III. Path with Minimal Cost

Now, assume every edge has a cost associated with it, we want to find out
the shortest paths. We therefore define a predicate


such that for any acyclic path P' from A to Z with cost C', C <= C'. 

For this purpose, we add the following clauses:

path_C(A, Z, Graph, Path, Cost) :- 
          path1_C(A, [Z], 0, Graph, Path, Cost).

path1_C(A, [A|Path1], Cost1, Graph, [A|Path1], Cost1).

path1_C(A, [Y|Path1], Cost1, graph(Nodes, Edges), Path, Cost) :-
          (member(e(X,Y), Edges); member(e(Y,X), Edges)),
          not_member(X, Path1),
          Cost2 is Cost1 + 1,
          path1_C(A, [X,Y|Path1], Cost2, graph(Nodes, Edges), Path, Cost).

shortest_path(A, Z, Graph, Path, Mini_Cost) :- 
         path_C(A, Z, Graph, Path, Mini_Cost),
         no_shorter_path(A, Z, Graph, Path1, Cost1, Mini_Cost).

no_shorter_path(A, Z, Graph, Path, Cost, Mini_Cost) :-
         not( and(A, Z, Graph, Path, Cost, Mini_Cost) ).

and(A, Z, Graph, Path, Cost, Mini_Cost) :- 
        path_C(A, Z, Graph, Path, Cost),
        Cost < Mini_Cost.

not(P) :- P, !, fail.