Post1up 18 Graphs
Post1up 18 Graphs
We can represent a node by a symbol (its name), and associate with each node a list of its
out-neighbours.
This is called the adjacency list representation.
More specifically, a graph is a list of pairs, each pair consisting of a symbol (the node’s
name) and a list of symbols (the names of the node’s out-neighbours).
This is very similar to a parent node with a list of children.
> Our example as data M18 6/45
(define g C F
'((A (C D E))
(B (E J))
A D
(C ())
(D (F J)) K
H
(E (K))
(F (K H))
E
(H ()) B
(J (H)) J
(K ()))
)
Recall that '(A (B C)) is a more compact way of writing (list 'A (list 'B 'C)). See
M14-25 for a review of quoted lists.
> Data definitions M18 7/45
To make our contracts more descriptive, we will define a Node and a Graph as follows:
;; A Node is a Sym
We can use the graph template to write a function that produces the out-neighbours of a
node. We’ll need this function in just a moment.
;; (neighbours v g) produces list of neighbours of v in g
;; Examples:
(check-expect (neighbours 'D g) (list 'F 'J)) C F
A
(check-expect (neighbours 'Z g) false) D
K
H
(define (neighbours v g)
(cond
[(empty? g) false]
[(symbol=? v (first (first g))) (second (first g))]
[else (neighbours v (rest g))]))
Write (count-out-neighbours g) which consumes a Graph and produces a
(listof Nat) indicating how many out-neighbours each Node in g has.
For example, with the sample graph
Ex. 1
(check-expect (count-out-neighbours g)
(list 3 2 0 2 1 2 0 1 0))
(check-expect (count-in-neighbours g)
(list 0 0 1 1 2 1 2 2 2))
(find-path 'A 'H g) ⇒ (list 'A 'D 'F 'H) or (list 'A 'D 'J 'H)
(find-path 'D 'H g) ⇒ (list 'D 'F 'H) or (list 'D 'J 'H)
Simple recursion does not work for find-path; we must use generative recursion.
If the origin equals the destination, the path consists of just this node.
Otherwise, if there is a path, the second node on that path must be an out-neighbour of the
origin node.
Each out-neighbour defines a subproblem (finding a path from it to the destination).
> Building a path from a solved sub-problem M18 12/45
C F
A D
K
H
E
B
J
> Backtracking algorithms M18 13/45
This is the same recursive pattern that we saw in the processing of expression trees and
evolutionary trees.
For expression trees, we had two mutually recursive functions, eval and apply.
Here, we have two mutually recursive functions, find-path and find-path/list.
> find-path M18 17/45
If we wish to trace find-path, trying to do a linear trace would be very long, both in terms of
steps and the size of each step. Our traces also are listed as a linear sequence of steps, but
the computation in find-path is better visualized as a tree.
We will use an alternate visualization of the potential computation (which could be
shortened if a path is found).
The next slide contains the trace tree. We have omitted the arguments dest and g which
never change.
» Tracing (find-path 'A 'B g) (2/2) M18 20/45
(find-path 'A)
(find-path/list '(C D E))
The only places where real computation is done on the graph is in comparing the origin to
the destination and in the neighbours function.
Backtracking can be used without having the entire graph available if the neighbours can be
derived from a “configuration”.
Board games: Puzzles:
https://www.puzzleprime.com/brain-teasers/deduction/eight-queens-puzzle/
» Backtracking in implicit graphs (2/3) M18 22/45
https://www.geeksforgeeks.org/minimax-algorithm-in-game-theory-set-3-tic-tac-
toe-ai-finding-optimal-move/
» Backtracking in implicit graphs (3/3) M18 23/45
The find-path functions for implicit backtracking look very similar to those we have
developed.
The neighbours function must now generate the set of neighbours of a node based on some
description of that node (e.g. the placement of pieces in a game).
This allows backtracking in situations where it would be inefficient to generate and store the
entire graph as data.
Backtracking in implicit graphs forms the basis of many artificial intelligence programs,
though they generally add heuristics to determine which neighbour to explore first, or which
ones to skip because they appear unpromising.
Termination of find-path (no cycles) M18 24/45
In a directed acyclic graph, any path with a given origin will recurse on its (finite number) of
neighbours by way of find-path/list. The origin will never appear in this call or any
subsequent calls to find-path: if it did, we would have a cycle in our DAG.
Thus, the origin will never be explored in any later call, and thus the subproblem is smaller.
Eventually, we will reach a subproblem of size 0 (when all reachable nodes are treated as
the origin).
Thus find-path always terminates for directed acyclic graphs.
> Non-termination of find-path (cycles) M18 25/45
It is possible that find-path may not terminate if there is a cycle in the graph.
Consider the graph . What if we try to find a path from A to D in this graph?
B '((A (B))
(B (C))
A D (C (A))
(D ()))
C
> Non-termination of find-path (cycles) M18 26/45
(find-path 'A)
B (find-path/list (list ‘B))
A D
(find-path 'B)
(find-path/list (list 'C))
C
(find-path 'C)
(find-path/list (list 'A))
(find-path 'A)
(find-path/list (list 'B))
...
Paths v2: Handling cycles M18 27/45
We can use accumulative recursion to solve the problem of find-path possibly not
terminating if there are cycles in the graph.
To make backtracking work in the presence of cycles, we need a way of remembering what
nodes have been visited (along a given path).
Our accumulator will be a list of visited nodes.
We must avoid visiting a node twice.
The simplest way to do this is to add a check in find-path/list.
> find-path/list M18 28/45
The code for find-path/list does not add anything to the accumulator (though it uses the
accumulator).
Adding to the accumulator is done in find-path/acc which applies find-path/list to the list
of neighbours of some origin node.
That origin node must be added to the accumulator passed as an argument to
find-path/list.
> find-path/acc M18 30/45
C F
A D
K H
E
B
J
> Tracing our examples (2/4) M18 32/45
Note that the value of the accumulator in find-path/list is always the reverse of the path
from A to the current origin (first argument).
> Tracing our examples (3/4) M18 33/45
This example has no cycles, so the trace only convinces us that we haven’t broken the
function on acyclic graphs, and shows us how the accumulator is working.
But it also works on graphs with cycles.
The accumulator ensures that the depth of recursion is no greater than the number of nodes
in the graph, so find-path terminates.
> Tracing our examples (4/4) M18 34/45
Backtracking now works on graphs with cycles, but it can be inefficient, even if the graph
has no cycles.
If there is no path from the origin to the destination, then find-path will explore every path
from the origin, and there could be an exponential number of them.
Paths v3: Efficiency M18 36/45
If there is no path from the origin to the destination, then find-path will explore every path
from the origin, and there could be an exponential number of them.
D1a D2a Z
D1 D2 D3 ... Y
D1b D2b
If there are d diamonds, then there are 3d + 2 nodes in the graph, but 2d paths from D1 to Y,
all of which will be explored.
> Understanding the problem (1/2) M18 37/45
There is no path from D1a to Z, so this will produce false, but in the process, it will visit all
the other nodes of the graph except D1b and Z.
find-path/list will then apply find-path/acc to D1b, which will visit all the same nodes
again.
D1a D2a Z
D1 D2 D3 ... Y
D1b D2b
> Understanding the problem (2/2) M18 38/45
When find-path/list is applied to the list of nodes nbrs, it first applies find-path/acc to
(first nbrs) and then, if that fails, it applies itself to (rest nbrs).
To avoid revisiting nodes, the failed computation should pass the list of nodes it has seen on
to the next computation.
It will do this by returning the list of visited nodes instead of false when it fails to find a path.
However, we must be able to distinguish this list from a successfully found path (also a list of
nodes).
D1a D2a Z
D1 D2 D3 ... Y
D1b D2b
> Remembering what the list of nodes represents M18 39/45
We will encapsulate each kind of list in its own structure. We can then easily use the
structure predicates (success? and failure?) to check whether the list of nodes represents
a path (success) or visited nodes (failure).
With these changes, find-path runs much faster on the diamond graph.
In future courses we will see how to make find-path even more efficient and how to
formalize our analyses.
Knowledge of efficient algorithms, and the data structures that they utilize, is an essential
part of being able to deal with large amounts of real-world data.
These topics are studied in CS 240 and CS 341 (for majors) and CS 234 (for non-majors).
Ex. 3 Write a function k-path-length which consumes a symbol start corresponding to a
node, a number k, and a graph. If there is a path with k or more edges originating from
start that does not repeat any nodes, the function produces one such path. Otherwise
the function produces false.
Note the use of string->symbol which we are not including as one of the “permitted
functions” on the last slide!
Write a function, graph-complement, that consumes a graph and produces its
complement. The complement of a graph g is a graph g ′ such that for each pair of
nodes u and v , (u, v ) is an edge in g ′ if and only if it is not an edge in g. Assume that
neither graph has edges from a node to itself. For example, the complement of
simple-graph is complement-graph:
Ex. 5
except that it is implemented using higher order functions, not explicit recursion.
Goals of this module M18 44/45
The following functions and special forms have been introduced in this module:
false? member?
You should complete all exercises and assignments using only these and the functions and
special forms introduced in earlier modules. The complete list is:
* + - ... / < <= = > >= abs add1 and append boolean? build-list ceiling char-alphabetic?
char-downcase char-lower-case? char-numeric? char-upcase char-upper-case?
char-whitespace? char<=? char<? char=? char>=? char>? char? check-error check-expect
check-within cond cons cons? cos define define-struct define/trace e eighth else
empty? equal? error even? exp expt false? fifth filter first floor foldl foldr fourth
integer? lambda length list list->string list? local log map max member? min modulo
negative? not number->string number? odd? or pi positive? quicksort quotient remainder
rest reverse round second seventh sgn sin sixth sqr sqrt string->list string-append
string-downcase string-length string-lower-case? string-numeric? string-upcase
string-upper-case? string<=? string<? string=? string>=? string>? string? sub1
substring symbol=? symbol? tan third zero?