In section 1 of this topic, we provided the fundamental functions for trees,
namely those than construct and destroy them. There are, however, some other
tree functions that make a tree library more complete. We'll discuss a few of
them here.

We have said that it is important to "hide" all of the details of an
implementation from the user. With that in mind, if that user is ever
going to need to check whether the tree is empty, then having a condition
`(tree == NULL)` would not be allowed. This implies that the programmer
knows that a NULL tree means an empty tree, and this is an implementation
detail. A more "black box" approach would be to have a boolean function
that returned a value indicating whether the tree was empty.

int is_empty(tree_t *tree)
{
return (tree == NULL);
}

Here we've taken the condition that the programmer would have put into the
program and wrapped it in a function that explains what the condition does.

Another boolean function that applies to a condition that comes up often tells
whether or not a given node is a leaf. The condition to check is simply
whether or not a node has any descendents. In other words, we simply need to
check to see if both children of the given node are NULL, which guarantees that
it has no descendants.

int is_leaf (tree_t *tree)
{
return (tree != NULL && tree->left == NULL && tree->right == NULL);
}

Here we make use of short circuit evaluation. When evaluating the
condition to return, the computer goes through each of the boolean expressions
and if any one of them is false, it will return false immediately.
This is how we guarantee that we never dereference a NULL pointer.

Another useful function is one to compute the depth of a tree. Again, as we
did with the `destroy_tree` function, we will use recursion. We know that
if the tree is empty, then the depth must be zero. Otherwise, the depth will
be one more than whichever is greater, the depth of the left subtree or of
the right subtree. To produce a function we simply translate these steps into
C.

int depth (tree_t *tree)
{
int left_depth, right_depth;
if (is_empty(tree)) {
return 0;
}
left_depth = depth (tree->left);
right_depth = depth (tree->right);
return 1 + (left_depth > right_depth? left_depth : right_depth);
}

There is almost no end to additional helper functions you could write for trees.
Doing the practice problems will hopefully trigger ideas for a few more. The
three we have provided should serve as examples for all of the potential
functions you may need. In addition, they should provide a framework for how
to go about thinking of necessary functions. In general, you should first
decide whether you need to go through the entire tree (or a section of it)
to solve the function and if so you should try to think of the problem
recursively.