What is the process of accessing either the left and right sub-trees?
You use pattern matching:
fun goLeft (NODE (_, l, _)) = l
| goLeft LEAF = raise Fail "Cannot go left here!"
fun goRight (NODE (_, _, r)) = r
| goRight LEAF = raise Fail "Cannot go right here!"
How can I [...] determine the maximum, the minimum, and if the element is present in the tree?
You build recursive functions that pattern match on sub-trees.
For example, there is no invariant in a binary tree that says that the minimal element has a fixed, known location in the tree. But this invariant is present in a binary search tree, in which all elements in every left sub-tree (including the root) are made sure to be less than or equal to all the elements of their right sub-trees.
The tree
5
/ \
3 8
/ / \
2 7 9
qualifies as a binary search tree because this invariant holds.
Finding the minimal element in such a tree means recursing in such a way that you always pick the left sub-tree until there is no left sub-tree, in which case you have found the minimum. How do you know when to stop recursion?
fun goLeeeft (NODE (_, l, _)) = goLeeeft l
| goLeeeft LEAF = "I've gone all the way left, but I have nothing to show for it."
fun minimum (NODE (x, l, r)) = (* is this the last non-leaf node? *)
| minimum LEAF = raise Empty (* whoops, we recursed too far! *)
When you pattern match one level deep, that is, on NODE (x, l, r)
, you only know that this is a non-leaf node, but you don't know the exact value located in the node, x
, and you don't know anything about the sub-structure of this node's left and right sub-trees.
You could go two ways here: Either make a helper function that tells you when to stop recursing, or pattern match one level deeper into l
. Here are two examples of that; they perform the same thing:
(* Using a helper function *)
fun isLeaf (NODE (_, _, _)) = false
| isLeaf LEAF = true
fun minimum (NODE (x, l, _)) =
if isLeaf l
then x
else minimum l
| minimum LEAF = raise Empty
(* Using direct pattern matching *)
fun minimum (NODE (x, LEAF, _)) = x
| minimum (NODE (x, l, _)) = minimum l
| minimum LEAF = raise Empty
Now maximum
writes itself.
As a curiosity, you can define generic recursion schemes such as folding on trees: Sml folding a tree