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:
  1. preorder
  2. inorder
  3. 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:
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:
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:
  1. We can make an array of data into a heap in time n log n
  2. 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
  3. 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.