CS 136 - Lecture 21

  1. Trees
  2. Tree definition and terminology
  3. Examples
    1. Expression trees
    2. Animals
  4. More terminology
  5. Binary tree interface

Trees

Trees come up naturally in many situations.

In a linear structure, every element has unique successor.

In trees, may have many.


Tree definition and terminology


DEFINITION: A tree is either empty or consists of a node, called the root node, together with a collection of (disjoint) trees, called its subtrees.
 

Note the analogy to family tree


Examples

Expression trees

(2*(4-1))+((2+7)/3)

Once we have an expression tree, how can we evaluate it?

Evaluate left subtree, evaluate right subtree, then perform operation at root.

Animals game - "20 questions"

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.


More terminology

Simple path is series of distinct nodes such that there is an edge between successive nodes. Height of node = length of longest path from that node to a leaf.
Height of tree = height of its root.
Depth of node is length of path from root to that node.
Degree of node is number of its direct descendents.

Level defined recursively:

  1. Root is at level 0.
  2. Level of any other node is one greater than level of its parent.
Level of a node also = length of path from root to that node.

Note. We'll stick to binary trees for now! (All nodes of degree <= 2.)
            Deal with oriented trees: Identify each subtree as being either left or right.


Lemma: If T is a binary tree, then at level k, T has <= 2k nodes.

Theorem: If T has height h then n = # nodes in T <= 2h+1-1. Equivalently, if T has n nodes, then n - 1 >= h >= log(n+1) - 1.


A full binary tree of height h has all leaves on level h.

A complete binary tree of height h is obtained from a full binary tree of height h with 0 or more (but not all) of the rightmost leaves at level h removed.

Say T is balanced if it has the minimum possible height for its # of nodes.


Binary tree interface

There are many alternative specifications of binary trees that allow insertion and deletion of elements. The specification that we use has an implicit reference into the tree, called a cursor. The methods moveLeft, moveRight, moveUp below allow the programmer to move the cursor through the tree. Unlike an iterator (which can traverse the tree, but not modify it), the cursor is used to locate a leaf to be deleted, or to move off a leaf in the direction in which a new element is to be inserted. See the pre-condition for insert, below, for instance.

The structures package doesn't actually include an interface for binary trees (it just contains one class BinaryTree), but here is what it would look like if it existed:

public interface BinaryTreeInterface {

    public void clear();
    // post: removes all nodes from tree

    public void insert(Object value);
    // pre: cursor is invalid
    // post: if tree empty, value is inserted at root, o'wise
    //       value inserted where cursor last moved off tree

    public Object remove();
    // pre: cursor is valid and has no children
    // post: leaf removed, cursor is moved to parent, if any

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

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

    public void reset();
    // post: moves the cursor to the root, if any

    public boolean valid();
    // post: returns true if cursor points to a valid node.

    public boolean hasLeft();
    // post: returns true iff cursor has left child

    public boolean hasRight();
    // post: returns true iff cursor has right child 

    public boolean hasParent();
    // pre: cursor is valid
    // post: returns true iff cursor has parent

    public boolean isLeftChild();
    // post: return true if cursor has parent & is left child

    public boolean isRightChild();
    // post: return true if cursor has parent & is rt child

    public void moveLeft();
    // pre: cursor is valid
    // post: cursor moves to left child of pre-cursor, 
    //       or off tree

    public void moveRight();
    // pre: cursor is valid
    // post: cursor moves to right child of pre-cursor, 
    //       or off tree

    public void moveUp();
    // pre: cursor is valid
    // post: cursor moves up to parent of pre-cursor

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

    public int depth();
    // post: returns depth of cursor in tree
    //       or -1 if tree is empty

    public boolean isFull();
    // post: return true iff subtree rooted at cursor is full

    public boolean isComplete();
    // post: returns true iff subtree rooted at cursor is 
    //        complete

    public boolean isEmpty();
    // post: returns true iff tree is emtpy

    public int size();
    // post: returns number of nodes in tree

    public Iterator elements();
    // post: returns inorder traversal of tree

    public Iterator inorderElements();
    // post: returns inorder traversal of tree

    public Iterator preorderElements();
    // post: returns preorder traversal of tree

    public Iterator postorderElements();
    // post: returns postorder traversal of tree

    public String toString();
    // post: returns string representation of tree
}

Tree traversals - visit all nodes in a tree
  1. pre-order (root first): root - left sub-tree - right sub-tree
  2. in-order (root in middle) - symmetric order: left sub-tree - root - right sub-tree
  3. post-order (root last): left sub-tree - right sub-tree - root
Notice what happens when you do these traversals of expression trees: Get prefix, infix, or postfix notation.
Most algorithms involve two parts:
  1. building tree,
  2. traversing it.
Why is recursion such an intuitive notion for trees?