Binary Search Trees in Prolog

Lecture #10
Complete the associated in-class exercises.

Table of Contents

1 Preface: BSTs (Nothing New for CPSC 312)

Note: We will likely skip past this section in class and use the example below to remind ourselves what a Binary Search Tree is.

Binary Search Trees (BSTs) are beautiful and profound… and not the point of CPSC 312. There’s absolutely nothing new from a 312 perspective in this short lecture. We’re just playing around with what full Prolog enables us to do, using compound terms as our data structures.

CPSC 107 and CPSC 110 discuss BSTs, and those of you who have taken CPSC 221 have certainly seen plenty of them. You’ve even already seen a Datalog-based binary tree representation in Assignment #3! (script/4 represents a binary tree of a different kind from a BST.)

We’re going to very briefly introduce BSTs and then focus on exploring some predicates that operate on BSTs.

1.1 What is a BST?

A BST is a data structure that associates keys (which must be ordered: comparable for <, equality, and >) with values (which can be anything). Depending on the flavour of BST you’re working with, they support efficient:


 

A BST is either:

We call the initial node of a non-empty BST its root.


 

As a result, when we’re searching for a key at a given node, only three cases can occur:

Also as a result, as long as the tree has about as many nodes in each node’s left subtree as in its right subtree: the number of nodes from the root to any other node in the tree is logarithmic in the size of the tree.


 

And.. that’s it! We have an awesome data structure for efficiently associating a key with a value. What are the keys and values? Could be anything, like:

2 BSTs in Prolog

Let’s start with an example BST to work with. Since our algorithms never “touch” the values, just pass them along unchanged, we often ignore them when we draw a tree, like this:

A BST with the keys written in but the values and empty trees left out.

How should Prolog represent this? Let’s use compound terms:

2.1 Example BST in Prolog

The root of our example tree has a key of 8. Values weren’t specified; let’s have the nodes values be a, b, c, … reading across from left to right in each row from top to bottom. So, the root’s value will be a.

Our root is therefore node(8, a, RootLeft, RootRight).

This is Prolog; so, we can actually keep going in that way. Here’s the whole left path down our tree:

Tree = node(8, a, RootLeft, RootRight), RootLeft = node(3, b, ThreeLeft, ThreeRight), ThreeLeft = node(1, d, empty, empty).

We could also directly write everything as a single nested structure:

Tree = node(8, a, 
         node(3, b,
           node(1, d, empty, empty),
           node(6, e, 
             node(4, g, empty, empty),
             node(7, h, empty, empty))),
         node(10, c,
           empty,
           node(14, f,
             node(13, i, empty, empty),
             empty))).

 

2.2 A Prolog Aside

It might be nice if our code above defined a constant Tree that we could use over and over. That’s what it does in Haskell and many other languages.

In Prolog, however, every variable is local to its own clause. If we use Tree anywhere else, it’s simply a new universally quantified variable in that clause.

Instead, we can sort-of define constants like this:

example_tree1(
  node(8, a, 
    node(3, b,
      node(1, d, empty, empty),
      node(6, e, 
        node(4, g, empty, empty),
        node(7, h, empty, empty))),
    node(10, c,
      empty,
      node(14, f,
        node(13, i, empty, empty),
        empty)))
)

% Now I can use that:
example_root_key1(RKey) :- example_tree1(node(RKey, _, _, _)).

% Or maybe better:

% root_key(Tree, Key) is true if Key is the key at the root of
% BST Tree.
root_key(node(Key, _, _, _), Key).

example_root_key1_v2(RKey) :- 
  example_tree1(Tree), root_key(Tree, RKey).

2.3 Live-Coding BSTs

Let’s work together on building some functionality for a BST. We’ll start with bst_starter.pl and put our working code in bst.pl.


 

(Three Exercises.)