Comp 271 Week 10
Trees in BlueJ
Note that expression trees are in fact discussed in Chapter 12.
TreeNode<T>
Note there are no constraints on T in this first example (in my bintree demo, not on the web)
TreeBuilder
Builds an Integer tree. Note that Integers can be compared with <, ==, >. Note that
searching the tree is fundamentally recursive. Doing this
nonrecursively, in fact, is a bit of a pain.
Also note that insert is built upon search, which in turn was designed
to facilitate insert because of what it returns (ie if a search fails,
the node where the new value would be inserted is returned, not null!)
Demo 2: use the recursive print method. This is called tree traversal, or, more specifically, inorder tree traversal or infix tree traversal.
Tree<T extends Comparable<T>>
This is a full-fledged tree of any type that extends Comparable. Except
it's not finished! We have to change Integer to T. Is there anything
else? (finished in bintree2, and in the lab 8 zip file)
Tree depth and size
Each of these can be calculated using a simple recursive approach.
Traversal
We can do any of three depth-first traversals:
- preorder
- inorder
- postorder
We always (for consistency, if nothing else) traverse the left subtree
before the right. The differences between the three above amount to
whether we visit the node's data before the left subtree, between the
left and right subtrees, or after the right subtree.
When we traverse expression trees,
inorder traversal gives us more or less what we had originally, though
we have to be careful about operator precedence. Postorder traversal
gives us "reverse Polish" or "reverse Lukasiewicz" notation; it works
well provided we understand that the operators must be strictly binary.
Demos evaluating RLN:
Single-digit numbers: 34+2*, 35*23*-
If we want multi-digit numbers, we need some separator. I'll use a
space; Hewlett-Packard popularized the ENTER key on their calculators.
15 23+2/
Evaluating RLN expressions can be done with a stack: push the operands
as we come to them, and when we come to the operator, pop the two
operands and push the result.
Lukasiewicz was apparently interested in the theoretical question of whether parentheses were necessary for unambiguous notation. With his notation, the answer is no.
Preorder and postorder are quite different in detail, but are clearly
closely related. Preorder traversal is equivalent to doing postorder
traversal of the mirror-image tree, and then reversing the result.
There is also breadth-first traversal, which is harder. We want to do each level in turn.
The recursive way of doing traversals is great, but we can't use that
in iterators. For that we need to keep track of our position with a
stack.
Demo of inorder traversal with a stack:
- start: push the leftmost branch
- at each node: pop a node, and if it has a right child, push the child's leftmost branch
Pushing the entire branch guarantees that we process that subtree (that
is, the left subtree) before returning to the current node.
Thursday
Random tree depth demo: use Builder class in demos/bintree (readjust size?)
Traversal
Another strategy on the stack approach:
- build stacks that can hold subtrees or data.
- initially push the root node
The algorithm is then:
while the stack is nonempty:
pop a node
if it's data, print it
if it's a tree, push its left and right subnodes and the data, in the appropriate order.
Pushing left, data, right gives inorder traversal.
Heaps
I have mixed feelings about heaps. On the one hand, they are historically important and the source of yet another n log n sort. And the representation of a "complete" tree as an array is important. On the other hand, they're weird.
A binary tree with N levels is complete if the first N-1 levels forms a full tree (all 2N-1
nodes are present at level N-1), and in looking at the bottom (Nth)
level, all leaves are present up to a point in left-to-right order, and
then there are none.
A complete binary tree can be stored compactly in an array, with the left child of the node stored at position i stored at 2i+1, and the right child at 2i+2. Demo.
A heap is a binary tree with values decreasing along any path to the root. However, a heap is not an ordered tree! In fact, the smallest element is always the root.
Heapsort makes use of the following three observations:
- We can make an array of data into a heap in time n log n
- We
can remove the root element (replacing it by promoting the smaller of
its two children, and continuing this process down the branch) in time
log n
- In light of 2, we can remove all n elements in sorted order in time n log n
We will only consider the last two ideas in class.
Associative Lookup
A classic compilers problem is writing a symbol table that allows
lookup of each identifier, with its attributes (type, size, allocated
location, etc). More generally, the problem is associative lookup: given some Key value, find the corresponding Data.