Consider the following simple printing routine for elements of a binary search tree:
public void printTree() {
left.printTree();
System.out.println(data);
right.printTree();
}
This is for a NodeTree; for an EmptyTree we just
have
public void printTree() {}
For an empty tree, we do nothing. For a non-empty tree, we print the
left subtree, then print the current data item, and then print the
right subtree. This will have the effect of printing a binary search
tree in increasing order. The method is recursive and, when
implemented, uses a stack of postponed method calls to visit
each node of the tree in turn.
Here we implement the stack directly, returning the item currently at the top of the stack. This can then be used for any purpose, not just for printing the tree. The stack needs to hold two components in each stack node. It needs the tree (or subtree) we are currently processing, together with a flag to indicate whether we are ready to return the data item at its root. We therefore define the class
private class StackItem {
NodeTree root;
boolean ready;
StackItem(NodeTree root, boolean ready) {
this.root = root;
this.ready = ready;
}
}
for private use in the stack. The code for the enumeration is then
given in Figure 8.5.
public Enumeration elementsInOrder() {
return new Enumeration() {
private Stack s = new StackArray();
{
s.push(new StackItem(NodeTree.this, false));
}
public boolean hasMoreElements() {
return !s.isEmpty();
}
public Object nextElement() {
StackItem next = (StackItem) s.pop();
while (!next.ready) {
NodeTree root = next.root;
if (!root.right.isEmpty()) {
s.push(new StackItem((NodeTree) root.right, false));
}
s.push(new StackItem(root, true));
if (!root.left.isEmpty()) {
s.push(new StackItem((NodeTree) root.left, false));
}
next = (StackItem) s.pop();
}
return next.root.data;
}
};
}
|
new StackItem((NodeTree) root.right, false);The basic idea of the nextElement method is this. First we pop the next item off the stack. If it is ``ready'' we return the root data value. Otherwise we push new items onto the stack until the top item is ready, and then we return its root data value. The distinction between being ``ready'' and not being ``ready'' enables us to use a NodeTree both to indicate a tree to be processed (not ready) and a root value to be printed (ready). The best way of understanding the algorithm is just to walk through its execution for a simple tree.
Note that the last two commands in the while loop consist of a push followed by a pop. It is possible to avoid this inefficiency at the cost of complicating the code. The modifications needed will be different for pre-order, post-order and in-order traversals. You can see how this might be implemented in the more complicated code of §18.4 of the Weiss textbook.