[Texas PLT logo]

COMP 202: Principles of Object-Oriented Programming II

  Tree Traversal Algorithms (Part 2)  

In-Order Traversal Revisited

Two instead of Three

In the previous lecture, we use three lambdas, _fRight, _fLeft and _fRoot, as variants to process a binary tree in in-order fashion.  Upon closer examination, we see that we can process directly the root of the tree inside of the _fLeft lambda and thereby eliminate to need for _fRoot.  We now have a second formulation of the in-order traversal process.

One instead of Two

At this point, we may as well ask, "why can't we just compose _fLeft with _fRight and have just one lambda instead?"  The answer is 'why not!'  Here is a version of in-order traversal using only one lambda as a variant.  In the code below, we also show how to copy a tree and make a mirror image of a tree by just using an appropriate and simplistic lambda.

Which formulation of in-order traversal  to use?

InOrder1 is apparently the easiest one to use since we only have to define one lambda.  Think of the InOrder2 and InOrder3 as similar black boxes with more knowbs to turn, thus offering more flexibility at the cost of ease of use.  In most cases, InOrder1 will be adequate.

To get a better feel of how to use InOrder2 and InOrder3, the students should do the following exercises.

Exercises

  1. Compute the clone and the mirror of a BiTree using InOrder2.
  2. Compute the clone and the mirror of a BiTree using InOrder3.

 

Hard-Coded InOrder/InOrder3/InOrder1 and Side-Effects

Here is the source code for a hard-coded in-order traversal, and for InOrder3 and InOrder1. Run the main methods in each of the InOrder* files and observe the System.err output (in red in DrJava).

Note that InOrderPrint and InOrder3 print the elements in-order. At the end, when four nodes have been inserted, the tree and the System.err output for InOrderPrint and InOrder3 is:

5
|_ -2
|  |_ []
|  |_ []
|_ 10
   |_ -9
   |  |_ []
   |  |_ []
   |_ []
-2
5
-9
10

For InOrder1, however, the output for the same tree is:

-2
-9
10
5

This is a post-order traversal! First, the left subtree is processed; it contains only -2. Then the right subtree is processed; here, the left subtree is processed first, printing -9, and then the root, 10. Then the root of the tree is printed, 5.

Yet, when we run the new InOrder1(concat3) visitor, we get as output the correct in-order string -2 5 -9 10. Why is that?

Before we can apply the ternary lambda in InOrder1,

we have to evaluate the arguments. That means we first solve the sub-problem for the left subtree, then solve the sub-problem for the right subtree, and then we apply the lambda. Therefore, the nodes in the left and right subtree are actually visited before we process the root.

The actual order in which we combine the result from recursing into the left subtree, processing the root, and recursing into the right subtree is determined solely in the lambda. Since we put the results in the order "left, root, right", we get an in-order string.

Printing to console is called a "side effect". It is something that happens in addition to the computation and that can be observed. Keeping a list in the lambda that stores the nodes in the order they are visited would also be a side effect, because the lambda then has state -- it "remembers" something about previous executions. Whenever the lambda for InOrder1 has side effects, then we can observe that InOrder1 actually visits the nodes in post-order, not in-order. We should therefore only write lambdas without side effects here.

 

Pre-Order and Post-Order Revisited

The pre-order and post-order tree traversal algorithms evidently have analogous revisions.  Their formulation should be straightforward.

 

Breadth-First Traversal

Consider the following binary tree.

               1
            /     \
         2         3
      /    \          \
    4      5           6
                       /
                    7

The breadth first traversal from left to right consists of visiting the nodes: 1, 2, 3, 4, 5, 6, 7 in this order.  To traverse the tree in this manner, each time we visit a non-empty node, we need to save its two children in a FIFO queue, process the current node, then go through the queue and process each node in the queue in the same manner.  Here is the code for printing a tree in breadth-first order.

What is the abstract processing here?  Use a lambda where you see System.out.println.  Here is the code for the invariant breadth-first tree traversal, where the variant is simply a lambda.

 

 

  Tree Traversal Algorithms (Part 2)  

URL: http://www.clear.rice.edu/comp202/08-fall/lectures/tree2/index.shtml
Copyright © 2008-2010 Mathias Ricken and Stephen Wong