CS136, Lecture 23

    1. Binary tree implementation

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

}

Try to 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 {
             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.
    {
            // 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.

Please study the code in the text!

The code for the iterators in the text is quite complex, because it simulates recursion with a stack. We could have approached cycling through nodes in a different way that does not involve iterators if we know exactly what we want to do to the value stored in each node - call it doValueOp();

public void doInorder()
{
    reset();    // move cursor to root
    if (root != null) doRecInorder();
}

/*
    post:  Do inorder traversal of subtree pointed to by 
            cursor.  Return cursor to starting point when done.
*/
public void doRecInorder()
{
    if (hasLeft())
    {
        moveLeft();
        doRecInorder();
        moveUp();
    }
    value().doValueOp();
    if (hasRight())
    {
        moveRight();
        doRecInorder();
        moveUp();
    }
}

It's very important to move the cursor back to where you started, otherwise the recursive solution will not work.

Is there an array implementation of trees?