CS 136 - Lecture 27

  1. Binary Search Trees
    1. Tree Sort
      1. Comparisons of advanced sorts
    2. Binary Search Tree Implementation

Binary Search Trees

Definition: A binary tree is a binary search tree iff it is empty or if the value of every node is both greater than or equal to every value in its left subtree and less than or equal to every value in its right subtree.

Note that a binary search tree is an ordered structure. That is, it maintains all of its elements in order.

One of many ways we can use a binary search tree is for sorting:


Tree Sort

We can build a binary search tree and then do an inorder traversal. Since the cost of entering an element into a (balanced) binary tree of size n is log n, the cost of building the tree is

(log 1) + (log 2) + (log 3) + ... + (log n) = O(n log n) compares.

Traversal is O(n). Total cost is O(n log n) in both the best and average cases.

The worst case is if the list is in order, it then behaves more like an insertion sort, creating a tree with one long branch. This results in a tree search as bad as O(n2).

In the worst case, Heap Sort is better, since it automatically keeps the tree in balance. In the average case, Tree Sort is good -- it also has the side effect of building an ordered structure that is useful and interesting for other reasons.


Comparisons of advanced sorts

Quicksort fastest on average - O(n log n)), but bad in worst case O(n2). HeapSort takes O(n log n) in average and worst case. MergeSort takes O(n log n) in average and worst case, O(n) extra space.

Binary Search Tree Implementation

Because it stores items in order, it is an implementation of OrderedStructure. We'll skip implementation of obvious methods, to focus on harder methods: add, get, and remove. We'll add protected methods locate, predecessor, and removeTop to make add, get, and remove easier.

public class BinarySearchTree implements OrderedStructure
{
    protected BinaryTreeNode root; 
    protected int count;

    public BinarySearchTree()
    // post: constructs an empty binary search tree.
    {
        root = null;
        count = 0;
    }

    public boolean isEmpty()
    // post: returns true iff binary search tree is empty.

    public void clear()
    // post: removes all elements from binary search tree

    public int size()
    // post: returns number of elements in binary search tree

    protected BinaryTreeNode locate(BinaryTreeNode subRoot,
                    Comparable value)
    // pre: subRootand value are non-null
    // post: returned: 1 - existing tree node with the desired value, or
    //                 2 - node to which value should be added.
    {
        Comparable subRootValue = (Comparable)subRoot.value();
        BinaryTreeNode child;

        // found at root: done
        if (subRootValue.equals(value)) return subRoot;
        // look left, if less, right if more
        if (subRootValue.lessThan(value))
          child = subRoot.right();
        else
          child = subRoot.left();
        // if no child there: not in tree, return this node,
        // else keep searching
        if (child == null) 
          return subRoot;
        else
          return locate(child, value);
    }

    protected BinaryTreeNode predecessor(BinaryTreeNode root)
    // pre: tree is not empty, root node has left child.
    // post: returns pointer to predecessor of root
    {
        Assert.pre(root != null, "No predecessor to middle value.");
        Assert.pre(root.left() != null, "Root has left child.");
        BinaryTreeNode result = root.left();
        while (result.right() != null)
          result = result.right();
        return result;
    }

    protected BinaryTreeNode successor(BinaryTreeNode root)
    // pre: tree is not empty, root node has right child.
    // post: returns pointer to successor of root
    {
        Assert.pre(root != null, "Tree is non-null.");
        Assert.pre(root.left() != null,"Root has right child.");
        BinaryTreeNode result = root.right();
        while (result.left() != null)
          result = result.left();
        return result;
    }

    public void add(Object val)
    // post: adds a value to the binary search tree.
    {
        BinaryTreeNode newNode = new BinaryTreeNode(val);

        // add value to binary search tree 
        // if there's no root, create value at root.
        if (root == null)
          root = newNode;
        else {
          Comparable value = (Comparable)val;
          BinaryTreeNode insertLocation = locate(root,value);
          Comparable nodeValue = (Comparable)insertLocation.value();
          // Location returned is the successor or predecessor
          // of the to-be-inserted value.
          if (nodeValue.lessThan(value))
            insertLocation.setRight(newNode);
          else {
            if (insertLocation.left() != null)
            // if value is in tree, we insert just before
              predecessor(insertLocation).setRight(newNode);
            else
              insertLocation.setLeft(newNode);
          }
        }
        count++;
    }

    public boolean contains(Object val)
    // post: returns true iff val is found within the tree
    {
        if (root == null) return false;

        BinaryTreeNode possibleLocation = locate(root,(Comparable)val);
        return val.equals(possibleLocation.value());
    }

    public Object get(Object val)
    // post: returns object found in tree, or null
    {
        if (root == null) return null;

        BinaryTreeNode possibleLocation = locate(root,(Comparable)val);
        if (val.equals(possibleLocation.value()))
          return possibleLocation.value();
        else
          return null;
    }

    public Object remove(Object val) 
    // post: removes one instance of val, if found
    {
        // remove value from a binary search tree
        // no root, just quit
        Comparable cval = (Comparable)val;

        if (isEmpty()) return null;
      
        if (val.equals(root.value())) // delete root value
        {
          BinaryTreeNode newroot = removeTop(root);
          count--;
          Object result = root.value();
          root = newroot;
          return result;
        } else {
          BinaryTreeNode location = locate(root,cval);

          if (cval.equals(location.value())) {
            count--;
            BinaryTreeNode parent = location.parent();
            if (parent.right() == location)
              parent.setRight(removeTop(location));
            else 
              parent.setLeft(removeTop(location));
            return location.value();
          }
        }
        return null;
    }

    protected BinaryTreeNode removeTop(BinaryTreeNode topNode)
    // pre: tree is not empty.
    // post: root of tree (topNode) is disconnected from tree 
    //       & new root is returned, new root has no parent.

    {
        // remove topmost BinaryTreeNode from binary search tree
        BinaryTreeNode left  = topNode.left();
        BinaryTreeNode right = topNode.right();
        // disconnect top node
        topNode.setLeft(null);
        topNode.setRight(null);
        // Case a, no left BinaryTreeNode
        //   easy: right subtree is new tree
        if (left == null)  return right; 
        // Case b, no right BinaryTreeNode
        //   easy: left subtree is new tree
        if (right == null) return left;
        // Case c, left node has no right subtree
        //   easy: make right subtree of left
        BinaryTreeNode predecessor = left.right();
        if (predecessor == null)
        {
          left.setRight(right);
          return left;
        }
        // General case, slide down left tree
        //   harder: predecessor of root becomes new root
        //           parent always points to parent of n
        BinaryTreeNode parent = left;
        while (predecessor.right() != null)
        {
          parent = predecessor;
          predecessor = predecessor.right();
        }
        // Assert: n is predecessor of root
        parent.setRight(predecessor.left());
        predecessor.setLeft(left);
        predecessor.setRight(right);
        return predecessor;
    }

    ...
}
Complexity of add, get, contains, and remove all proportional to height of tree. If balanced, then O(log n), otherwise O(n) in worst case.