280 likes | 396 Views
Formal Models of Computation Part II The Logic Model. Lecture 7 – Trees and graphs. Trees. Trees are important data structures Binary trees: each node has at most two daughters Binary trees can be recursively defined as An empty tree is a binary tree (represented as “nil”)
E N D
Formal Models of ComputationPart IIThe Logic Model Lecture 7 – Trees and graphs
Trees • Trees are important data structures • Binary trees: each node has at most two daughters • Binary trees can be recursively defined as • An empty tree is a binary tree (represented as “nil”) • A binary tree is a node with two subtrees • In Prolog, we can represent binary trees as bTree(Node,SubTreeL,SubTreeR) where SubTreeL and SubTreeR are (recursively) binary trees. • Convention for the empty tree: nil formal models of computation
Trees (Cont’d) a b c d e f nil nil nil nil nil nil nil • For instance, the binary tree • Can be represented in Prolog as bTree(a, bTree(b, bTree(d,nil,nil), bTree(e,nil,nil)), bTree(c, bTree(f,nil,nil), nil)). • Remarks: • There is nothing special about the bTree name! We could have used “foo” or “bar” ; • Identations are for the benefit of humans, i.e., to improve visualisation! formal models of computation
Trees (Cont’d) • Let’s write a program to visit the nodes of a binary tree in pre-order • Visit (write) parent node, then • Visit subtree on the left (using the same policy), then • Visit subtree on the right (ditto) • In Prolog: preOrder(nil). % empty tree? preOrder(bTree(N,L,R)):- % not empty tree? write(N), % write node N nl, % skip a line preOrder(L), % recursion on left tree preOrder(R). % recursion on right tree formal models of computation
Trees (Cont’d) • Let’s alter the previous program • Enable it to collect in a list the nodes of the tree: • This portion defines the main flow of execution • Add an argument to hold the list to be built: • Decide on what to do with each case… traverse(nil). % empty tree? traverse(bTree(N,L,R)):- % not empty tree? traverse(L), % recursion on left tree traverse(R). % recursion on right tree traverse(nil,List0). traverse(bTree(N,L,R),List1):- traverse(L,List2), traverse(R,List3). formal models of computation
Trees (Cont’d) • What happens on the base (empty tree) case? • What value should a list of nodes have when the tree is empty? Answer: the empty list! collect(nil,List0):- % if empty tree List0 = []. % list is empty collect(bTree(N,L,R),List1):- collect(L,List2), collect(R,List3). formal models of computation
Trees (Cont’d) • What happens on the recursive case? • We must relate N and the resulting lists of L and R • Node N must be the head of the final resulting list • There are two resulting lists List2 and List3 • These two lists must be combined (append!) collect(nil,List0):- % if empty tree List0 = []. % list is empty collect(bTree(N,L,R),List1):- List1 = [N|List4], % N is the head collect(L,List2), % get nodes of L collect(R,List3), % get nodes of R append(List2,List3,List4). % append them! formal models of computation
Trees (Cont’d) • More compact: collect(nil,[]). % if empty tree collect(bTree(N,L,R),[N|Ns]):- % otherwise collect(L,LNs), % get nodes of L collect(R,RNs), % get nodes of R append(LNs,RNs,Ns). % append them! formal models of computation
Trees (Cont’d) • Change program to count nodes of tree • Initial program establishes the flow of execution • As the tree is traversed, computations can take place! • To compute/show results, an argument must be inserted in every goal: traverse(nil). traverse(bTree(N,L,R)):- traverse(L), traverse(R). count(nil,Arg1). count(bTree(N,L,R),Arg2):- count(L,Arg3), count(R,Arg4). formal models of computation
Trees (Cont’d) • The inserted arguments must be • Defined or • Related to each other count(nil,Arg1):- define(Arg1). count(bTree(N,L,R),Arg2):- relate1(Arg2,Arg3,Arg4), count(L,Arg3), count(R,Arg4), relate2(Arg2,Arg3,Arg4). formal models of computation
Trees (Cont’d) • In this specific case, we have: • More compact: count(nil,SNs):- SNs = 0. count(bTree(N,L,R),SNs):- count(L,SLNs), count(R,SRNs), SNs is 1 + SLNs + SRNs. count(nil,0). % empty tree? no. nodes is 0 count(bTree(N,L,R),SNs):- % otherwise count(L,SLNs), % get no nodes of left tree count(R,SRNs), % get no nodes of right tree SNs is 1 + SLNs + SRNs. % add them up plus 1 formal models of computation
Graphs a b c e d f • Graphs are a generalisation of trees: • Nodes can have many parents and many children • Example: • A mathematical definition: • A graph G = (N,E ) is • A set of nodes N = {a1 ,…, an} • A set of edges E N N formal models of computation
Graphs (Cont’d) a b c e d f • A Prolog representation for graphs • Based on the previous mathematical definition: nodes([a,b,c,d,e,f]). edge(a,b). edge(a,d). edge(a,f). edge(b,c). edge(b,f). edge(c,e). edge(f,d). edge(f,e). formal models of computation
Graphs (Cont’d) • Let’s write a program to check for paths • A path between O and D exists iff: • There is an edge (O,D) (i.e., a direct path) OR • There is an edge (O,I) from the origin to an intermediate node I and there is a path from I to D • In Prolog: path(O,D):- % there is a path between O and D edge(O,D). % if there is an edge between them path(O,D):- % otherwise, edge(O,I), % there is an intermediary node I path(I,D). % with a path between I and D formal models of computation
Graphs (Cont’d) path(O1,D1):- edge(O1,D1). {O1/a,D1/e} unification path(O1,D1):- edge(O1,D1). {O1/a,D1/e} Fail!! • Execution: nodes([a,b,c,d,e,f]). edge(a,b). edge(a,d). edge(a,f). edge(b,c). edge(b,f). edge(c,e). edge(f,d). edge(f,e). path(O,D):- edge(O,D). path(O,D):- edge(O,I), path(I,D). Backtracks… ?- path(a,e). formal models of computation
Graphs (Cont’d) path(O1,D1):- edge(O1,I1), path(I1,D1). {O1/a,D1/e} unification path(b,e) • Execution: nodes([a,b,c,d,e,f]). edge(a,b). edge(a,d). edge(a,f). edge(b,c). edge(b,f). edge(c,e). edge(f,d). edge(f,e). path(O,D):- edge(O,D). path(O,D):- edge(O,I), path(I,D). nodes([a,b,c,d,e,f]). edge(a,b). edge(a,d). edge(a,f). edge(b,c). edge(b,f). edge(c,e). edge(f,d). edge(f,e). path(O,D):- edge(O,D). path(O,D):- edge(O,I), path(I,D). path(O1,D1):- edge(O1,I1), path(I1,D1). {O1/a,D1/e,I1/b} path(O1,D1):- edge(O1,I1), path(I1,D1). {O1/a,D1/e,I1/b} ?- path(a,e). formal models of computation
Graphs (Cont’d) path(O1,D1):- edge(O1,D1). {O1/b,D1/e} unification path(O1,D1):- edge(O1,D1). {O1/b,D1/e} Fail!! • Execution: nodes([a,b,c,d,e,f]). edge(a,b). edge(a,d). edge(a,f). edge(b,c). edge(b,f). edge(c,e). edge(f,d). edge(f,e). path(O,D):- edge(O,D). path(O,D):- edge(O,I), path(I,D). nodes([a,b,c,d,e,f]). edge(a,b). edge(a,d). edge(a,f). edge(b,c). edge(b,f). edge(c,e). edge(f,d). edge(f,e). path(O,D):- edge(O,D). path(O,D):- edge(O,I), path(I,D). Backtracks… path(b,e) formal models of computation
Graphs (Cont’d) path(O1,D1):- edge(O1,I1), path(I1,D1). {O1/b,D1/e} unification path(c,e) • Execution: nodes([a,b,c,d,e,f]). edge(a,b). edge(a,d). edge(a,f). edge(b,c). edge(b,f). edge(c,e). edge(f,d). edge(f,e). path(O,D):- edge(O,D). path(O,D):- edge(O,I), path(I,D). nodes([a,b,c,d,e,f]). edge(a,b). edge(a,d). edge(a,f). edge(b,c). edge(b,f). edge(c,e). edge(f,d). edge(f,e). path(O,D):- edge(O,D). path(O,D):- edge(O,I), path(I,D). path(O1,D1):- edge(O1,I1), path(I1,D1). {O1/b,D1/e,I1/c} path(O1,D1):- edge(O1,I1), path(I1,D1). {O1/a,D1/e,I1/c} path(b,e) formal models of computation
Graphs (Cont’d) path(O1,D1):- edge(O1,D1). {O1/c,D1/e} unification • Execution: nodes([a,b,c,d,e,f]). edge(a,b). edge(a,d). edge(a,f). edge(b,c). edge(b,f). edge(c,e). edge(f,d). edge(f,e). path(O,D):- edge(O,D). path(O,D):- edge(O,I), path(I,D). nodes([a,b,c,d,e,f]). edge(a,b). edge(a,d). edge(a,f). edge(b,c). edge(b,f). edge(c,e). edge(f,d). edge(f,e). path(O,D):- edge(O,D). path(O,D):- edge(O,I), path(I,D). path(O1,D1):- edge(O1,D1). {O1/c,D1/e} path(c,e) path(c,e) ?- path(a,e). yes ?- formal models of computation
Graphs (Cont’d) • Predicate path can be used in 4 different ways: • path(a,e): is there a path between a and e? • path(a,D): where can we get from a? • path(O,e): where can we start a journey to e? • path(O,D): all possible tours • Problems: the previous program may loop! • If there are cycles in the path, then program may loop forever… • We can improve the previous program to keep track of where we’ve been and avoid loops. • Store in a list the nodes that have been visited formal models of computation
Graphs (Cont’d) “not provable” • Reuse previous program and add functionality: path(O,D,Path):- % as before edge(O,D). % Path has no use here path(O,D,Path):- % same as before edge(O,I), % get intermediate node \+ member(I,Path), % that is not already in Path path(I,D,[I|Path]). % update path and carry on formal models of computation
Graphs (Cont’d) Loop: a-b-a!! Initial value of Path • Same graph with one node added: edge(b,a) nodes([a,b,c,d,e,f]). edge(a,b). edge(a,d). edge(a,f). edge(b,a). edge(b,c). edge(b,f). edge(c,e). edge(f,d). edge(f,e). path(O,D,_):- edge(O,D). path(O,D,Path):- edge(O,I), \+ member(I,Path), path(I,D,[I|Path]). nodes([a,b,c,d,e,f]). edge(a,b). edge(a,d). edge(a,f). edge(b,a). edge(b,c). edge(b,f). edge(c,e). edge(f,d). edge(f,e). path(O,D,_):- edge(O,D). path(O,D,Path):- edge(O,I), \+ member(I,Path), path(I,D,[I|Path]). ?- path(a,e,[a]). formal models of computation
Graphs (Cont’d) path(O1,D1,_):- edge(O1,D1). {O1/a,D1/e} unification path(O1,D1,_):- edge(O1,D1). {O1/a,D1/e} Fail!! • Execution: nodes([a,b,c,d,e,f]). edge(a,b). edge(a,d). edge(a,f). edge(b,a). edge(b,c). edge(b,f). edge(c,e). edge(f,d). edge(f,e). path(O,D,_):- edge(O,D). path(O,D,Path):- edge(O,I), \+ member(I,Path), path(I,D,[I|Path]). nodes([a,b,c,d,e,f]). edge(a,b). edge(a,d). edge(a,f). edge(b,a). edge(b,c). edge(b,f). edge(c,e). edge(f,d). edge(f,e). path(O,D,_):- edge(O,D). path(O,D,Path):- edge(O,I), \+ member(I,Path), path(I,D,[I|Path]). Backtracks… ?- path(a,e,[a]). formal models of computation
Graphs (Cont’d) path(O1,D1,Path1):- edge(O1,I1), \+ member(I1,Path1), path(I1,D1,[I1|Path1]). {O1/a,D1/e,Path1/[a]} unification path(b,e,[b,a]) • Execution: nodes([a,b,c,d,e,f]). edge(a,b). edge(a,d). edge(a,f). edge(b,a). edge(b,c). edge(b,f). edge(c,e). edge(f,d). edge(f,e). path(O,D,_):- edge(O,D). path(O,D,Path):- edge(O,I), \+ member(I,Path), path(I,D,[I|Path]). nodes([a,b,c,d,e,f]). edge(a,b). edge(a,d). edge(a,f). edge(b,a). edge(b,c). edge(b,f). edge(c,e). edge(f,d). edge(f,e). path(O,D,_):- edge(O,D). path(O,D,Path):- edge(O,I), \+ member(I,Path), path(I,D,[I|Path]). nodes([a,b,c,d,e,f]). edge(a,b). edge(a,d). edge(a,f). edge(b,a). edge(b,c). edge(b,f). edge(c,e). edge(f,d). edge(f,e). path(O,D,_):- edge(O,D). path(O,D,Path):- edge(O,I), \+ member(I,Path), path(I,D,[I|Path]). path(O1,D1,Path1):- edge(O1,I1), \+ member(I1,Path1), path(I1,D1,[I1|Path1]). {O1/a,D1/e,Path1/[a],I1/b} path(O1,D1,Path1):- edge(O1,I1), \+ member(I1,Path1), path(I1,D1,[I1|Path1]). {O1/a,D1/e,Path1/[a],I1/b} path(O1,D1,Path1):- edge(O1,I1), \+ member(I1,Path1), path(I1,D1,[I1|Path1]). {O1/a,D1/e,Path1/[a],I1/b} ?- path(a,e,[a]). formal models of computation
Graphs (Cont’d) path(O2,D2,Path2):- edge(O2,I2), \+ member(I2,Path2), path(I2,D2,[I2|Path2]). {O2/b,D2/e,Path2/[b,a]} unification path(O2,D2,Path2):- edge(O2,I2), \+ member(I2,Path2), path(I2,D2,[I2|Path2]). {O2/a,D2/e,Path2/[b,a],I2/a} Fail!! • Execution: nodes([a,b,c,d,e,f]). edge(a,b). edge(a,d). edge(a,f). edge(b,a). edge(b,c). edge(b,f). edge(c,e). edge(f,d). edge(f,e). path(O,D,_):- edge(O,D). path(O,D,Path):- edge(O,I), \+ member(I,Path), path(I,D,[I|Path]). nodes([a,b,c,d,e,f]). edge(a,b). edge(a,d). edge(a,f). edge(b,a). edge(b,c). edge(b,f). edge(c,e). edge(f,d). edge(f,e). path(O,D,_):- edge(O,D). path(O,D,Path):- edge(O,I), \+ member(I,Path), path(I,D,[I|Path]). nodes([a,b,c,d,e,f]). edge(a,b). edge(a,d). edge(a,f). edge(b,a). edge(b,c). edge(b,f). edge(c,e). edge(f,d). edge(f,e). path(O,D,_):- edge(O,D). path(O,D,Path):- edge(O,I), \+ member(I,Path), path(I,D,[I|Path]). path(O2,D2,Path2):- edge(O2,I2), \+ member(I2,Path2), path(I2,D2,[I2|Path2]). {O2/b,D2/e,Path2/[b,a],I2/a} path(O2,D2,Path2):- edge(O2,I2), \+ member(I2,Path2), path(I2,D2,[I2|Path2]). {O2/a,D2/e,Path2/[b,a],I2/a} It fails because node a has already been visited – it is in the Path followed so far. Prolog backtracks to the previous goal and tries another edge! path(b,e,[b,a]). formal models of computation
Graphs (Cont’d) path(O2,D2,Path2):- edge(O2,I2), \+ member(I2,Path2), path(I2,D2,[I2|Path2]). {O2/b,D2/e,Path2/[b,a]} unification path(c,e,[c,b,a]) • Execution: nodes([a,b,c,d,e,f]). edge(a,b). edge(a,d). edge(a,f). edge(b,a). edge(b,c). edge(b,f). edge(c,e). edge(f,d). edge(f,e). path(O,D,_):- edge(O,D). path(O,D,Path):- edge(O,I), \+ member(I,Path), path(I,D,[I|Path]). nodes([a,b,c,d,e,f]). edge(a,b). edge(a,d). edge(a,f). edge(b,a). edge(b,c). edge(b,f). edge(c,e). edge(f,d). edge(f,e). path(O,D,_):- edge(O,D). path(O,D,Path):- edge(O,I), \+ member(I,Path), path(I,D,[I|Path]). path(O2,D2,Path2):- edge(O2,I2), \+ member(I2,Path2), path(I2,D2,[I2|Path2]). {O2/b,D2/e,Path2/[b,a],I2/c} path(O2,D2,Path2):- edge(O2,I2), \+ member(I2,Path2), path(I2,D2,[I2|Path2]). {O2/a,D2/e,Path2/[b,a],I2/c} path(O2,D2,Path2):- edge(O2,I2), \+ member(I2,Path2), path(I2,D2,[I2|Path2]). {O2/a,D2/e,Path2/[b,a],I2/c} path(b,e,[b,a]). formal models of computation
Graphs (Cont’d) • Execution: path(O3,D3,Path3):- edge(O3,I3), \+ member(I3,Path3), path(I3,D3,[I3|Path3]). {O3/c,D3/e,Path3/[c,b,a]} nodes([a,b,c,d,e,f]). edge(a,b). edge(a,d). edge(a,f). edge(b,a). edge(b,c). edge(b,f). edge(c,e). edge(f,d). edge(f,e). path(O,D,_):- edge(O,D). path(O,D,Path):- edge(O,I), \+ member(I,Path), path(I,D,[I|Path]). nodes([a,b,c,d,e,f]). edge(a,b). edge(a,d). edge(a,f). edge(b,a). edge(b,c). edge(b,f). edge(c,e). edge(f,d). edge(f,e). path(O,D,_):- edge(O,D). path(O,D,Path):- edge(O,I), \+ member(I,Path), path(I,D,[I|Path]). Computation goes on until it finally reaches destination. The path is added with each node visited thus avoiding coming back to a visited node. path(c,e,[c,b,a]) formal models of computation