CS136, Lecture 22

    1. Examples using trees
      1. Parse Trees:
      2. Animals game
    2. Binary tree implementation

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.)

Animals game

Next homework assignment.

Game is held as tree with answers at leaves and questions as internal nodes.

Asking questions corresponds to path from root to a leaf, where leaf holds animal which is best guess.

If guess wrong, then add new animal and corresponding question (w/old animal, too) in tree in place of old animal.

Game is much more interesting if save old games so can build tree again.

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.

Unfortunately can't use files in applet. However it is easy to convert an applet into an application. Add main method (static) as follows:

  /*
    post:  If program is being run as an application, creates 
           a frame for it to run in.
           Ignored if run as applet.
  */
  public static void main(String[] args)
  {
      // Set up and show frame
    AnimalAppletFrame app = new AnimalAppletFrame("Animals Game");
    app.resize(400,160);
    app.show();
  }

and add a new class at the end of the file containing your applet class:

/*
  This class supplied a frame (window) to be used when the 
        program is run as an application.
*/
class AnimalAppletFrame extends Frame
{
  /*
    Create frame holding the animals applet.
  */
  public AnimalAppletFrame(String frameTitle)
  {
    super(frameTitle);            // Put title on window
    
    // create the animals applet and stick it in the middle 
    // of the frame (filling it!)
    AnimalApplet applet = new AnimalApplet();

    // Tell the applet it is being run as an application
    applet.setApplication();        
    add("Center",applet);
    applet.init();                // Initialize the applet
    applet.start();               // Start the applet running
  }
}

Just set up project to be an application and can run as application (and can still use as applet as well - just use applet project).

File operations are pretty straightforward:

    // create a new output file with name fileName.
  DataOutputStream outFile = 
            new DataOutputStream(new FileOutputStream(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(); 
  outfile.close();                    // close file

    // create a new input file with name fileName.
  DataInputStream inFile = 
            new DataInputStream(new FileInputStream(fileName));
        // write character from letter onto outFile
  outFile.writeChar(letter);          
        // write string from contents onto outFile
  outFile.writeUTF(contents);         
  inFile.close();                     // close file

Binary tree implementation

As expected, we use linked nodes for primary implementation:

public class BinaryTreeNode
{
    protected Object val;
    protected BinaryTreeNode parent;
    protected BinaryTreeNode left;
    protected BinaryTreeNode right;

    public BinaryTreeNode(Object value,
              BinaryTreeNode left,
              BinaryTreeNode right) 
    // post: returns a node referencing value & subtrees
    {
            val = value;
            setLeft(left);
            setRight(right);
    }

    public BinaryTreeNode left()
    // post: returns reference to left subtree, or null

    public BinaryTreeNode right()
    // post: returns reference to right subtree, or null

    public BinaryTreeNode parent()
    // post: returns reference to parent node, or null

    public void setLeft(BinaryTreeNode newLeft)
    // post: sets left subtree to newLeft
    //       reparents newLeft if not null
    {
            if (left != null &&
                (left.parent() == this)) left.setParent(null);
            left = newLeft;
            if (left != null) left.setParent(this);
    }

    public void setRight(BinaryTreeNode newRight)

    protected void setParent(BinaryTreeNode newParent)
    // post: reparents this node to parent reference, or null

    public static int size(BinaryTreeNode n)
    // post: returns the size of the subtree rooted at n

    public static BinaryTreeNode root(BinaryTreeNode n)
    // post: returns the root of the tree containing node n
    {
            if ((n == null) || (n.parent() == null)) return n;
            else return root(n.parent());
    }

    public static int height(BinaryTreeNode n)
    // post: returns the height of a node n in its tree
    {
            if (n == null) return -1;
            return 1 + Math.max(height(n.left()),height(n.right()));
    }

    public static int depth(BinaryTreeNode n)
    // post: returns the depth of a node in the tree
    {
            if (n == null) return -1;
            return 1 + depth(n.parent());
    }
    ...    

    public boolean isLeftChild()
    // post: returns true if this is a left child of parent.
    {
            if (parent() == null) return false;
            return this == parent().left();
    }

    public Object value()
    // post: returns value associated with this node.

    public void setValue(Object value)
    // post: sets the value associated with this node

}

Come back and talk about iterators later. Nothing really surprising above.

Notice must be careful in setting element to left or right (must connect both ways) - extra test at beginning setting parent field of elt currently there to null isn't really necessary.

Notice root, height, and depth are all recursive (and are all static - i.e., belong with class, not object!).

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 {
             cursor = new BinaryTreeNode(value);
             if (wentLeft) {
                prior.setLeft(cursor);
             } else {
                prior.setRight(cursor);
             }
            }
            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.
    {
            // be aware that the cursor could become null if
            // a precondition is violated.  Otherwise the cursor
            // is null only if the tree is empty
            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?