--------------------------------------------------------------------------------
The material in this manual is produced by Chris Nevison, Computer Science Department,
Colgate University, Hamilton, NY 13346.  It has been modified by Valerie Chu.
--------------------------------------------------------------------------------

Binary Trees
An important structure which can be built dynamically using pointers is the binary tree.
Conceptually a binary tree is a structure where each node may contain some information,
and may have two child nodes, called left and right for convenience. These child nodes themselves
may also each have two child nodes, and so on.  A node may also have only a left child
or only a right child, or no child nodes at all.  A node with no child node is called a "leaf" node.
All other nodes are called "interior" nodes.
Here are some examples:



 

Exercise 1:  Examine the tree T1 above to see if you find any pattern or order to the arrangement.
Can you think of any interpretation of the structure of the tree T1?

Exercise 2:  Examine the tree T2 above to see if you find any pattern or order to the arrangement.
Can you think of any interpretation of the structure of the tree T2?

Exercise 3:  Examine the tree T3 above to see if you find any pattern or order to the arrangement.
Can you think of any interpretation of the structure of the tree T3?

A Tree Node

A node in a binary tree contains a field for the information stored
at that node and two pointer fileds, one for the left child and one
for the right child.  For example, a binary tree containing strings
could have its nodes defined as follows:

classTreeNode {
  public:
  string info;
  TreeNode * left;
  TreeNode * right;
  TreeNode();      // default constructor
  TreeNode(conststring & item, TreeNode * l, TreeNode * r);
};

TreeNode:: TreeNode()
: info(""), left(NULL), right(NULL)
{}

// constructor assigns info to be item, left to be l and right to be r.
TreeNode:: TreeNode(const string & item, TreeNode * l, TreeNode * r)
: info(item), left(l), right(r)
{}

With this definition, the following declaration of a pointer to a node and
statement would create a new node containing the string "example".

TreeNode * ptr;
ptr = new TreeNode("example", NULL, NULL);

At this point in the code the node pointed to by ptr would contain the string
"example" in its data field and both its child pointers would be NULL, meaning
it has no children.

Creating a Binary Tree

A binary tree is created by first creating a single node with both child pointers NULL.
This node forms the simplest non-empty tree.  The additional nodes are created and inserted
into the tree to build a larger and larger tree.  The pattern of the tree and its interpretation
depend on how additional nodes are inserted. The simplest means of adding a node to a tree
is to create a new node with both child pointers NULL and make it the child of a node in the
tree with at least one NULL child.  Even in this case, different patterns can be developed,
depending on how the attachment location is selected.  Here is one example:

void Insert(TreeNode * & T, const string & name)
// pre: T is not NULL
{
  if(name < T->info){
    if(T->left == NULL)
      T->left = new TreeNode(name, NULL, NULL);
    else
      Insert(T->left, name);
  }
  else{
    if(T->right == NULL)
      T->right = new TreeNode(name, NULL, NULL);
    else
      Insert(T->right, name);
  }
}
Note how the search for an insertion point is done recursively.  This is quite common.

Exercise 4:  Which of the example trees T1, T2, T3 given above could have been created
with this insertion function?

Tree Traversals

Many operations on trees can be characterized as "traversals" of some sort.
To traverse a structure means to "visit" each item stored in the structure and
carry out some operation.  For example, we might want to find the largest integer
in the tree given as example T2 above.  This would involve traversing the tree and
comparing each data item with the maximum so far.  We might want to print out all
the entries in a tree, as for example with the tree T1 above, which contains a list of names.
In this case the visit to a node would consist of printing its contents.
For trees, traversals are most commonly (but not exclusively) done using recursion.
Think of traversing a tree as the following three operations, in no particular order:

1.  Visit the item stored at this (root) node.
2.  Traverse the left sub-tree
3.  Traverse the right sub-tree.

If we have defined a function Visit, which carries out the operation on a node of a tree
for a traversal, then we could implement a traversal of a tree, using a function Traverse, as follows:

void Traverse(TreeNode * t)
{
1   if(t != NULL){
2     Visit(t);
3     Traverse(t->left);
4     Traverse(t->right);
  }
}

In line 1 we check for a null pointer, in which case this (sub)tree is empty and there
is nothing to be done.  Then lines 2, 3, 4, carry out the steps suggested above.  Note
that there is nothing magic about the order of steps 2, 3, 4.  If we did these operations
in a different order we would traverse the whole tree, but visit the nodes in a different
order.  Sometimes the order of the visits is important, in which case we need to select
the ordering of steps 2, 3, 4 correctly.

There are six ways in which the three steps can be placed in order.  Three of those orderings
are commonly used and are given names.  We usually view trees with a crude sense of order,
namely that the left side comes before the right side, so the three common traversal orderings
always have the recursive call to traverse the left subtree before the recursive call to traverse the
right subtree.  Then there are three variations:

The three variations are given below:

void Preorder(TreeNode * t)
//  executes a preorder traversal of the tree t
{
  if(t != NULL){
    Visit(t);
    Preorder(t->left);
    Preorder(t->right);
}

void Inorder(TreeNode * t)
//  executes an inorder traversal of the tree t
{
  if(t != NULL){
    Inorder(t->left);
    Visit(t);
    Inorder(t->right);
}

void Postorder(TreeNode * t)
//  executes a postorder traversal of the tree t
{
  if(t != NULL){
    Postorder(t->left);
    Postorder(t->right);
    Visit(t);
}

For example, if the function visit simple printed the contents of the node, we could use
each of these to traverse the list given in example T1 above and print it out:

void Visit(TreeNode * t)
{
  cout << t->info << endl;
}

 Exercise 5:  Write out the list which would be printed for each of the three traversals
using the given Visit function applied to the tree T1 above.