Comp202: Principles of Object-Oriented Programming II
Fall 2007 -- 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 sum = (Integer)host.getLeftSubTree().execute(this);
        sum +=(Integer)host.getRootDat();
        sum += (Integer)host.getRightSubTree().execute(this);
        return sum;
    }

}

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, fRoot, fLeft, b) = b;
 *
 * non-empty case: 
 *   InOder(tree, fRight, fRoot, fLeft, b) = 
 *     fRight(InOrder3(tree.right, fRight, fRoot, fLeft, b),
 *            fRoot(tree, 
 *                  fLeft(InOrder3(tree.left, fRight, fRoot, fLeft, b), b),
 *                  b),
 *            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 fRoot, ILambda fLeft) {
        _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(host.getRightSubTree().execute(this, b),
                            _fRoot.apply(host, 
                                        _fLeft.apply(host.getLeftSubTree().execute(this, b), 
                                                     b),
                                        b),
                            b);
    } 

    public static void main(String[] nu) {
	
	ILambda passThru = new ILambda() {
            public Object apply(Object ... params) {
			    return params[0];
	        }
	    }

        ILambda addToRoot = new ILambda() {
            public Object apply(Object ... params) {
                // assume params[0] is a non-empty BiTree:
                return ((Integer)((BiTree)params[0]).getRootDat())+(Integer)params[1]);  
            }
        };

        ILambda add = Add.Singleton;

        // Add the numbers in the tree in in-order fashion:
        IVisitor inOrderAdd = new InOrder3(add,addToRoot, passThru);

      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();
                }
            }
        };
        
        ILambda concatToRoot = new ILambda() {
            public Object apply(Object ... params) {
                // assume params[0] is a non-empty BiTree:
                return concat.apply(((BiTree)params[0]).getRootDat(), params[1]);  
            }
        };


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

        // in in-order fashion:

        IVisitor inOrderConcat = new InOrder3(concat, concatToRoot, passThru);

        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:33 CDT

©2007 Stephen Wong and Dung Nguyen