Comp202: Principles of Object-Oriented Programming II
Fall 2007 -- Lecture #13: Traversing Binary Trees (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.

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 two lambdas as variants.

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

 * empty case: 

 *   InOrder2(empty, fRight, fLeft, b) = b;

 * non-empty case: 

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

 *     fRight(fLeft(InOrder2(tree.left, fRight, fLeft, b), tree)), 

 *            InOrder2(tree.right, fRight, fLeft, b));

 * @author DXN

 * @author SBW

 * @since 09/22/2004

 */

public class InOrder2 implements IVisitor {

    // an abstract function with domain (range of InOrder2, BiTree):

    private ILambda _fLeft;

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

    private ILambda _fRight;

    
    public InOrder2(ILambda fRight, ILambda fLeft) {

        _fRight = fRight;

        _fLeft = fLeft;

    }

    
    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), host), 

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

    }

    
    public static void main(String[] nu) {

        final ILambda add = Add.Singleton;

        ILambda add2 = new ILambda() {

            public Object apply(Object ... params) {

                return add.apply(params[0], ((BiTree)params[1]).getRootDat());

            }

        };

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

        IVisitor inOrderAdd = new InOrder2(add, add2);

        
        final 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 concat2 = new ILambda() {

            public Object apply(Object ... params) {

                return concat.apply(params[0], ((BiTree)params[1]).getRootDat());

            }

        };

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

        // in in-order fashion:

        IVisitor inOrderConcat = new InOrder2(concat, concat2);

        
        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, ""));                

    }

}

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.

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 one lambda as variant.

 * Let f be an ILambda and b be some input object.

 * empty case: 

 *   InOrder1(empty, f, b) = b;

 * non-empty case: 

 *   InOder(tree, f, b) = f(InOrder1(tree.left, f, b), tree, InOrder1(tree.right, f, b));

 * @author DXN

 * @author SBW

 * @since 09/22/2004

 */

public class InOrder1 implements IVisitor {

    private ILambda _f;
    public InOrder1(ILambda f) {

        _f = f;

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

        return b; 

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

        return _f.apply(host.getLeftSubTree().execute(this, b), 

                        host,

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

    }
    public static void main(String[] nu) {

        final ILambda add = Add.Singleton;

        ILambda add3 = new ILambda() {

            public Object apply(Object ... params) {

                return add.apply(params[0], ((BiTree)params[1]).getRootDat(), params[2]);

            }

        };

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

        IVisitor inOrderAdd = new InOrder1(add3);

        
        final 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 concat3 = new ILambda() {

            public Object apply(Object ... params) {

                return concat.apply(concat.apply(params[0], ((BiTree)params[1]).getRootDat()), params[2]);

            }

        };        

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

        // in in-order fashion:

        IVisitor inOrderConcat = new InOrder1(concat3);
        ILambda makeTree = new ILambda () {

            public Object apply(Object ... params) {

                BiTree result = new BiTree();

                result.insertRoot(((BiTree)params[1]).getRootDat());

                result.setLeftSubTree((BiTree)params[0]);

                result.setRightSubTree((BiTree)params[2]);

                return result;

            }

        };

        // Cloning a BiTree:

        IVisitor inOrderClone = new InOrder1(makeTree);
        ILambda makeMirror = new ILambda () {

            public Object apply(Object ... params) {

                BiTree result = new BiTree();

                result.insertRoot(((BiTree)params[1]).getRootDat());

                result.setLeftSubTree((BiTree)params[2]);

                result.setRightSubTree((BiTree)params[0]);

                return result;

            }

        };

        // Mirror image of a BiTree:

        IVisitor inOrderMirror = new InOrder1(makeMirror);



        BiTree bt = new BiTree();

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

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

        System.out.println("Cloning \n" + bt.execute(inOrderClone, new BiTree())); 

        System.out.println("Mirror Tree \n" + bt.execute(inOrderMirror, new BiTree()) + '\n'); 
        bt.insertRoot(5);

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

        System.out.println("Cloning \n" + bt.execute(inOrderClone, new BiTree())); 

        System.out.println("Mirror Tree \n" + bt.execute(inOrderMirror, new BiTree()) + '\n'); 
        bt.getLeftSubTree().insertRoot(-2);

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

        System.out.println("Cloning \n" + bt.execute(inOrderClone, new BiTree())); 

        System.out.println("Mirror Tree \n" + bt.execute(inOrderMirror, new BiTree()) + '\n'); 
        bt.getRightSubTree().insertRoot(10);

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

        System.out.println("Cloning \n" + bt.execute(inOrderClone, new BiTree())); 

        System.out.println("Mirror Tree \n" + bt.execute(inOrderMirror, new BiTree()) + '\n'); 
        bt.getRightSubTree().getLeftSubTree().insertRoot(-9);

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

        System.out.println("Cloning \n" + bt.execute(inOrderClone, new BiTree())); 

        System.out.println("Mirror Tree \n" + bt.execute(inOrderMirror, new BiTree()) + '\n'); 
        System.out.println("Done!");

    }

}

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.

 

Pre-Order and Post-Order Revisited

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

 


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

©2007 Stephen Wong and Dung Nguyen