Creating Binary Tree in Prolog

Viewed 705

I'm a beginner in Prolog, and my first assignment is to implement a function construct(), that builds a binary tree from a list. I know there is something wrong or missing in my code, but I can't put my finger on it. I also think a helper method may be necessary, but I can't think of how to go about that. Here is my code so far:

construct([],nil).
construct(E, tree(E,nil,nil)).
construct([H|T], tree(H, construct(T,R), nil)):- 
          T>H.
construct([H|T], tree(H, construct(T,R), nil)):- 
          T<H.
2 Answers

First: there are no functions in Prolog, just predicates. So, if you write tree(H, construct(T,R), nil) (from your 2nd clause), then you've created a structure (or "term") that looks like this:

            tree
         /   |     \
       /     |       \
       H  construct  nil
           /   \
          /     \
         T       R

which is not what you want.

Instead, you should write something like this (although this won't do what you want either -- see below):

construct([H|T], tree(H, Tree, nil)):- 
    T>H,
    construct(T, R, Tree).

Second: you should have an easy way to distinguish a value from a non-value. You've chosen nil for non-value, so you could have something like value(V) for storing a value.

Now, think about what a node looks like. It's got 2 parts (left and right), each of which can be either nil, a value or a node. For example, you'd want [1,2,3] to produce a tree that looks something like

node(node(value(1),
          value(2)),
     value(3))

As for an auxiliary predicate (not "function"), an obvious one is a predicate that inserts a single value to a tree. So, just list out all the possibilities:

insert_value(X, node(nil, nil), node(value(X), nil)).  % insert into an empty node
insert_value(X, node(value(Y), nil), node(value(X), value(Y))) :- X < Y.
insert_value(X, node(value(Y), nil), node(value(Y), value(X))) :- X > Y.
insert_value(X, node(node(A,B), value(Y)), node(T1, value(Y))) :-
    X < Y,
    insert_value(X, node(A,B), T1).

etc.

If you do this, you'll find that this isn't the best representation for a tree, but I'll leave it to you to come up with a better representation. (Incidentally, there's no need for all the node functors to have 2 elements; it's ok to have node - contains nothing; node(X) - contains a single item; node(X,Y) - contains 2 items).

Also, you'll need to decide what to do wwhen inserting a value that's already in the tree -- does insert_value/3 fail, does it just do nothing, or does it allow multiple identical values?

Once you've got this working, then you can write a predicate to insert a list of values:

insert_values([], Tree, Tree).
insert_values([X|Xs], Tree0, Tree) :-
    insert_value(X, Tree0, Tree1),
    insert_values(Xs, Tree1, Tree).

This is a very common pattern; the general form of it is called "foldl".

And finally, get the whole thing going with an initial value of an empty node:


insert_values(Values, Tree) :-
    insert_values(Values, node(nil,nil), Tree).

It's also very common in Prolog to define predicates with different numbers of arguments; in this case, insert_values/2 uses insert_values/3 with an initial value of an empty node (node(nil,nil)).

In Prolog, we can use:

  • the atom nil to represent an empty binary tree, and
  • a term of the form t(Left, Root, Right) to represent a non-empty binary tree, where Root is the value at the root of the tree, and Left and Right are also terms representing binary trees.

To make the binary trees easier to see, you can use the following predicate:

show(T) :-
    show(T, 0).

show(nil, _).
show(t(Left, Root, Right), Indent) :-
    Indent1 is Indent + 3,
    show(Right, Indent1),
    format('~*c~w\n', [Indent, 32, Root]),
    show(Left, Indent1).

Example:

?- show( t(t(nil,1,nil), 2, t(nil,3,nil)) ).
   3
2
   1
true.

Let List be a list denoting the in-order traversal of an arbitrary binary tree, as illustrated bellow:

enter image description here

Then, since different binary trees can have the same in-order traversal, a binary tree corresponding to such list can be described as follows:

% convert from in-order traversal list to binary tree

list_to_bt(List, Tree) :-
    (   List = []
    ->  Tree = nil
    ;   Tree = t(Left, Root, Right),
        append(Prefix, [Root|Suffix], List),
        list_to_bt(Prefix, Left),
        list_to_bt(Suffix, Right) ).

Example:

?- list_to_bt([1,2,3], T), show(T).
      3
   2
1
T = t(nil, 1, t(nil, 2, t(nil, 3, nil))) ;
   3
      2
1
T = t(nil, 1, t(t(nil, 2, nil), 3, nil)) ;
   3
2
   1
T = t(t(nil, 1, nil), 2, t(nil, 3, nil)) 
...
false.

If you want to obtain only balanced binary trees (e.g., trees such that, for each node, the absolute difference in the sizes of the right and left subtrees is at most 1), then you can include this constraint as follows:

% convert from in-order traversal list to balanced binary tree (bbt)

list_to_bbt(List, Tree) :-
    (   List = []
    ->  Tree = nil
    ;   Tree = t(Left, Root, Right),
        append(Prefix, [Root|Suffix], List),
        length(Prefix, Size1),
        length(Suffix, Size2),
        abs(Size1 - Size2) =< 1,
        list_to_bbt(Prefix, Left),
        list_to_bbt(Suffix, Right) ).

Example:

?- list_to_bbt([1,2,3], T), show(T).
   3
2
   1
T = t(t(nil, 1, nil), 2, t(nil, 3, nil)) ;
false.

If you want only balanced binary search trees from an arbitrary list, then you must sort this list before creating the balanced binary tree:

% convert from arbitrary list to balanced binary search tree (bbst)

list_to_bbst(List, Tree) :-
    sort(List, Sorted),
    list_to_bbt(Sorted, Tree).

Examples:

?- list_to_bbst([3,1,7,5,4,2,6], T), show(T).
      7
   6
      5
4
      3
   2
      1
T = t(t(t(nil, 1, nil), 2, t(nil, 3, nil)), 4, t(t(nil, 5, nil), 6, t(nil, 7, nil))) ;
false.

?- list_to_bbst([3,1,4,2], T), show(T).
      4
   3
2
   1
T = t(t(nil, 1, nil), 2, t(nil, 3, t(nil, 4, nil))) ;
   4
      3
2
   1
T = t(t(nil, 1, nil), 2, t(t(nil, 3, nil), 4, nil)) ;
   4
3
      2
   1
T = t(t(nil, 1, t(nil, 2, nil)), 3, t(nil, 4, nil)) ;
   4
3
   2
      1
T = t(t(t(nil, 1, nil), 2, nil), 3, t(nil, 4, nil)) ;
false.
Related