CS136, Lecture 22

    1. Binary Tree Implementation
    2. Files for Animals
    3. Examples using trees
      1. Parse Trees:

Binary Tree Implementation

BinaryTree class relatively straightforward, but watch cursor:
public class BinaryTree {
    protected BinaryTreeNode root;   // root of the tree
    protected BinaryTreeNode cursor; // ptr to current node
    protected BinaryTreeNode prior;  // cursor's prior value
            // cursor result of moving left
    protected boolean wentLeft;      
    protected int size;          // the size of the tree

    public BinaryTree()
    // post: creates an empty binary tree
    {
            clear();
    }

    public void clear()
    // post: removes all nodes from tree
    {
            root = cursor = null;
            size = 0;
    }

    public void insert(Object value)
    // pre: cursor is invalid
    // post: if tree empty, value inserted at root, otherwise
    //       value inserted where cursor last moved off tree
    {
            Assert.pre(cursor == null, "Insertion does not overwrite value.");
            if (prior == null) {
              Assert.pre(root == null, "Insertion at root only in empty tree.");
              cursor = root = new BinaryTreeNode(value);
            } else {
             if (wentLeft) {
                prior.setLeft(cursor = new BinaryTreeNode(value));
             } else {
                prior.setRight(cursor = new BinaryTreeNode(value));
             }
            }
            size++;
    }

    public Object remove()
    // pre: cursor is valid and has no children
    // post: leaf is removed, cursor moved to parent, if any
    {
            Assert.pre(cursor != null, "Node to be removed exists.");
            Assert.pre(!(hasLeft()||hasRight()), "Node to be removed is leaf.");
            Object value = cursor.value();
            if (isLeftChild()) {
                moveUp();
                cursor.setLeft(null);
            } else if (isRightChild()) {
                moveUp();
                cursor.setRight(null);
            } else {
                root = cursor = prior = null;
            }
            size--;
            return value;
    }

    public Object value()
    // pre: cursor valid
    // post: returns value of object at cursor
    {
            return cursor.value();
    }

    public void setValue(Object value)
    // pre: cursor valid
    // post: sets value found at cursor
    {
            cursor.setValue(value);
    }

    public void reset()
    // post: moves the cursor to the root, if any
    {
            cursor = root;
            prior = null;
    }

    public boolean valid()
    // post: returns true if cursor points to a valid node.
    {
            return cursor != null;
    }

    public boolean hasLeft()
    // post: returns true iff cursor has left child
    {
            return (cursor != null) && (cursor.left() != null);
    }

    public boolean isLeftChild()
    // post: return true if cursor has parent & is left child
    {
            return (cursor != null) && cursor.isLeftChild();
    }

    public void moveLeft()
    // pre: cursor is valid
    // post: cursor moves to left child of pre-cursor, or off tree
    {
            prior = cursor;
            wentLeft = true;
            cursor = cursor.left();
    }

    public int height()
    // post: returns height of cursor in tree
    //       or -1 if tree is empty
    {
            return BinaryTreeNode.height(cursor);
    }
}

Reasonably efficient in terms of time and space complexity. 3 references per node, but makes operations easier. Can also get away w/ just left and right subtree references.

Is there an array implementation of trees?


Files for Animals

Suggest writing out in either prefix or postfix traversal. Indicate whether question or answer by writing 'Q' or 'A', otherwise might not be able to reconstruct tree.

Can load in exactly same way: read root, read and attach left subtree, read and attach right subtree.

File operations are pretty straightforward:

        // create a new input file with name fileName.
  DataInputStream inFile = new DataInputStream(new FileInputStream(fileName));
        // read character from inFile and store in letter
  char letter = inFile.readChar();    
        // read string from inFile and store in contents
  String contents = inFile.readUTF(); 
  inFile.close();                     // close file

        // create a new output file with name fileName.
  DataOutputStream outFile = new DataOutputStream(new FileOutputStream(fileName));
        // write character from letter onto outFile
  outFile.writeChar(letter);          
        // write string from contents onto outFile
  outFile.writeUTF(contents);         
  outfile.close();                    // close file

These classes are found in package java.io, so you must include an import statement for them.

File operations can throw exceptions because not find file, hit end of file when reading, etc.! In Java, one must test for exceptions and provide code to execute when exceptions are raised.

Java file operations may raise exceptions of type IOException. Must surround code that may throw this exception by:

try
    {//code using files} 
catch (IOException exc)
    {//code to execute if exception raised}

In the above, exc is a parameter of type IOException. You can send it a toString message to get info on what went wrong in the block that follows it.

You can either put all of the code into one single try-catch block or put each command that may raise the exception in such a block. You will get syntax errors if you do not surround these file messages with a try-catch block.

Your program should not just ignore these exceptions. At a minimum it should print out a message and return to a consistent state.

Examples using trees

Parse Trees:

The following are syntax diagrams for arithmetic expressions.

What do we know about first possible characters of Factor, Term, or Expression?

Can use to help catch errors.

Look at how 3 * 7 + 6 / 2 - (3 + 7) would be understood as a tree.

3*7 is a term because both 3 and 7 are factors, similarly 6/2 is a term.

(3+7) is a factor because 3 + 7 is is an expression.

Because it is a factor, it is also a term.

Therefore 3 * 7 + 6 / 2 - (3 + 7) is of the form term1 + term2 - term3, and hence is an expression!

Exercise: Write out the corresponding tree (remember operations of the same precedence are done from left to right.)

Once you have the tree, how do you evaluate it?

See Parser code on-line.

Two things to notice:

  1. Mutually recursive routines: expression, term, and factor in class Parser (using input from class Lexer).

  2. Each node in tree from class corresponding to its type! (I.e., AddNode for addition, TimesNode for multiplication, NumNode for leaves, etc.)