|
|
Comp202: Principles of Object-Oriented Programming II
|
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.
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
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; } }
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, ""));
}
}
Here is what it means to process a tree by traversing it in in order.
Write an invariant post order traversal visitor analogous to the above in order traversal visitor.
Here is what it means to process a tree by traversing it in in order.
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