Comp202: Principles of Object-Oriented Programming II
Fall 2006 -- Lecture #12: Traversing Binary Trees   


When we process a list, we basically have two ways to traverse it and move data along: forward (as in forward accumulation) or reverse (as in reverse accumulation).  In contrast, due to its non-linear structure, a binary tree can be traversed in many more different ways.  However, we can categorize data movement in two "directions": top-down and bottom-up.  In this lecture, we will study five tree traversal algorithms that are most common in tree processing.

  1. In order traversal
  2. Pre order traversal
  3. Post order traversal
  4. Breadth-first traversal
  5. Depth-first traversal

For the sake of definiteness, we consider the following trees as examples throughout:

mtTree: an empty tree

biTree: the non-empty tree

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

 

In Order Traversal

Here is what it means to process a tree by traversing it in in order.

Here is a concrete example of an in-order traversal of the above biTree.

package brs.visitor;

import brs.*;
/**

 * Add all the numbers in a tree in in-order.

 * @author DXN

 */

public class InOrderAdd implements IVisitor {

    public static final InOrderAdd Singleton = new InOrderAdd();    

    private InOrderAdd() {

    }

    
    public Object emptyCase(BiTree host, Object nu) {

        return 0; 

    }
    public Object nonEmptyCase(BiTree host, Object nu) {

        Integer left = (Integer)host.getLeftSubTree().execute(this, null);

        Integer root = (Integer)host.getRootDat();

        Integer right = (Integer)host.getRightSubTree().execute(this, null);

        return left + root + right;

    }

}

In class Exercise 1:

Write a visitor that prints the contents of a binary in in-order traversal.  What should the output be for the above biTree?

As you can see, there is so much similarity between the code for exercise 1 and InOrderAdd.  The question is whether or not we can separate the variants from the invariant and capture the abstraction of in-order traversal as the invariant.

Clearly, tree traversal is a visitor. What does it mean to "process the root"?  How can we enforce the order of processing?

Processing the root and each of the subtrees: use an abstract function ILambda; the concrete ILambdas are the variants.

Enforcing the order of processing: use the order in which the arguments of of a function are evaluated; this is the invariant.

So here is the invariant code for  in-order tree traversal.

package brs.visitor;

import brs.*;

import fp.*;
/**

 * Traverse a binary tree in order:

 * For an empty tree:

 * do the appropriate processing.

 * For a non-empty tree:

 * Traverse the left subtree in order;

 * Process the root;

 * Traverse the right subtree in order;

 * 

 * Uses 3 lambdas as variants.

 * Let fRight, fLeft, fRoot be ILambda and b be some input object.

 * empty case: 

 *   InOrder3(empty, fRight, fLeft, fRoot, b) = b;

 * non-empty case: 

 *   InOder(tree, fRight, fLeft, fRoot, b) = 

 *     fRight(fLeft(InOrder3(tree.left, fRight, fLeft, fRoot, b), fRoot(tree)), 

 *            InOrder3(tree.right, fRight, fLeft, fRoot, b));

 * @author DXN

 * @author SBW

 * @since 09/22/2004

 */

public class InOrder3 implements IVisitor {

    // an abstract function on non-empty BiTrees only:

    private ILambda _fRoot;

    
    // an abstract function with domain (range of InOrder3, range of _fRoot):

    private ILambda _fLeft; 

    
    // an abstract function with domain (range of _fLeft, range of InOrder3):

    private ILambda _fRight;

    
    public InOrder3(ILambda fRight, ILambda fLeft, ILambda fRoot) {

        _fRight = fRight;

        _fLeft = fLeft;

        _fRoot = fRoot;

    }

    
    public Object emptyCase(BiTree host, Object b) {

        return b; 

    }
    public Object nonEmptyCase(BiTree host, Object b) {

        return _fRight.apply(_fLeft.apply(host.getLeftSubTree().execute(this, b), 

                                          _fRoot.apply(host)), 

                             host.getRightSubTree().execute(this, b));

    }

    
    public static void main(String[] nu) {

        ILambda getRoot = new ILambda() {

            public Object apply(Object ... params) {

                // assume params[0] is a non-empty BiTree:

                return ((BiTree)params[0]).getRootDat();  

            }

        };

        ILambda add = Add.Singleton;

        // Add the numbers in the tree in in-order fashion:

        IVisitor inOrderAdd = new InOrder3(add, add, getRoot);

        
        ILambda concat = new ILambda() {

            public Object apply(Object ... params) {

                if ("" != params[0].toString()) {

                    if ("" != params[1].toString()) {

                        return params[0].toString() + " " + params[1].toString();

                    }

                    else {

                        return params[0].toString();

                    }

                }

                else {

                    return params[1].toString();

                }

            }

        };

        // Concatenate the String representation of the elements in the tree 

        // in in-order fashion:

        IVisitor inOrderConcat = new InOrder3(concat, concat, getRoot);

        
        BiTree bt = new BiTree();

        System.out.println(bt + "\nAdd \n" + bt.execute(inOrderAdd, 0));

        System.out.println("In order concat \n" + bt.execute(inOrderConcat, "")); 

        
        bt.insertRoot(5);

        System.out.println(bt + "\nAdd \n" + bt.execute(inOrderAdd, 0));

        System.out.println("In order concat \n" + bt.execute(inOrderConcat, ""));  

        
        bt.getLeftSubTree().insertRoot(-2);

        System.out.println(bt + "\nAdd \n" + bt.execute(inOrderAdd, 0));

        System.out.println("In order concat \n" + bt.execute(inOrderConcat, ""));    

        
        bt.getRightSubTree().insertRoot(10);

        System.out.println(bt + "\nAdd \n" + bt.execute(inOrderAdd, 0));

        System.out.println("In order concat \n" + bt.execute(inOrderConcat, "")); 

        
        bt.getRightSubTree().getLeftSubTree().insertRoot(-9);

        System.out.println(bt + "\nAdd \n" + bt.execute(inOrderAdd, 0));

        System.out.println("In order concat \n" + bt.execute(inOrderConcat, ""));                

    }

}
 

Post Order Traversal

Here is what it means to process a tree by traversing it in in order.

In class exercise 2:

Write an invariant post order traversal visitor analogous to the above in order traversal visitor.

 

Pre Order Traversal

Here is what it means to process a tree by traversing it in in order.

In class exercise 2:

Write an invariant pre order traversal visitor analogous to the above in order traversal visitor.

 


Last Revised Thursday, 03-Jun-2010 09:52:24 CDT

©2006 Stephen Wong and Dung Nguyen