public class BinarySearchTree implements OrderedStructure { protected BinaryTreeNode root; protected int count; public BinarySearchTree() // post: constructs an empty binary search tree. { root = null; count = 0; } 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 shd be added. protected BinaryTreeNode predecessor(BinaryTreeNode root) // pre: tree is not empty, root node has left child. // post: returns pointer to predecessor of root protected BinaryTreeNode successor(BinaryTreeNode root) // pre: tree is not empty, root node has right child. // post: returns pointer to successor of root public void add(Object val) // post: adds a value to the binary search tree. 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: successor of root becomes new root // parent always points to parent of n BinaryTreeNode parent = left; while (predecessor.right() != null) { parent = predecessor; predecessor = predecessor.right(); } 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), owise O(n) in worst case.
Can we guarantee that methods have complexity O(log n)?
AVL trees (which keep a measure of the differences of heights of subtrees in each node) guarantee remain balanced and therefore ops fast.
Splay trees need not be balanced, but average performance guaranteed O(log n) (like vector additions!).
Rotate right in order to move y to root. Moves everything in a up by one, while elts in c go down by one (and elts in b stay same level). Notice that rotation preserves ordering in binary search tree.
Similarly for left rotation.
Code from BinaryTreeNode:
protected void rotateRight() // pre: this node has a left subtree // post: rotates local portion of tree so left child is root { BinaryTreeNode parent = parent(); BinaryTreeNode newRoot = left(); boolean wasChild = parent != null; boolean wasLeftChild = isLeftChild(); // hook in right child of new root to left of old root setLeft(newRoot.right()); // put this to right of new root below it newRoot.setRight(this); // attach newRoot to parent if (wasChild) { if (wasLeftChild) parent.setLeft(newRoot); else parent.setRight(newRoot); } }Idea behind splay tree. Every time find, get, add, or remove an element x, move it to the root by a series of rotations.
Splay means to spread outwards
Tree can get more unbalanced w/ splay operations, but ave depth of nodes on original path is (on average) cut in half. Therefore if repeatedly look for same elts, then will find much faster (happens in practice).
Skip actual code to do splay - follows rules given earlier, but a bit ugly - look at it on-line.
Example:
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); if (val.equals(possibleLocation.value())) { root = possibleLocation // node becomes root splay(root); return true; } else return false; }
Others are similar.
public void doPreorder() { reset(); // move cursor to root if (root != null) doRecPreorder(); } /* post: Do preorder traversal of subtree pointed to by cursor. Return cursor to starting point when done. */ public void doRecPreorder() { value().doValueOp(); if (hasLeft()) { moveLeft(); doRecInorder(); moveUp(); } if (hasRight()) { moveRight(); doRecPreorder(); moveUp(); } }Only problem is can't take one step, and then stop and save where are so can come back later to do it. No problem with iterative since save stack.
Idea is top element on stack is element currently examining. Must traverse subtree headed by node on stack as well as all subtrees headed by all other nodes on stack.
All but nextElement() are pretty trivial:
class BinaryTreePreorderIterator implements Iterator { protected BinaryTreeNode root; // root of tree protected Stack todo; // helper stack public BinaryTreePreorderIterator(BinaryTreeNode root) // post: constructs an iterator to traverse in preorder { todo = new StackList(); this.root = root; reset(); } public void reset() // post: resets the iterator to retraverse { while (!todo.isEmpty()) s.pop(); // stack is empty. Push on the current node. if (root != null) todo.push(root); } public boolean hasMoreElements() // post: returns true iff iterator is not finished { return !todo.isEmpty(); } public Object value() // pre: hasMoreElements() // post: returns reference to current value { return ((BinaryTreeNode)todo.peek()).value(); } public Object nextElement() // pre: hasMoreElements(); // post: returns current value, increments iterator { BinaryTreeNode old = (BinaryTreeNode)todo.pop(); Object result = old.value(); if (old.right() != null) todo.push(old.right()); if (old.left() != null) todo.push(old.left()); return result; } }
This code for nextElement() is simpler than code in text and on-line.
class BinaryTreeInorderIterator implements Iterator { protected BinaryTreeNode root; // root of tree protected Stack todo; // helper stack public BinaryTreeInorderIterator(BinaryTreeNode root) // post: constructs an iterator to traverse inorder { todo = new StackList(); this.root = root; reset(); } public void reset() // post: resets the iterator to retraverse { while (!s.isEmpty()) todo.pop(); // stack is empty. Push on nodes from root to // leftmost descendent BinaryTreeNode current = root; while (current != null) { todo.push(current); current = current.left(); } } public boolean hasMoreElements() // post: returns true iff iterator is not finished { return !todo.isEmpty(); } public Object value() // pre: hasMoreElements() // post: returns reference to current value { return ((BinaryTreeNode)todo.peek()).value(); } public Object nextElement() // pre: hasMoreElements(); // post: returns current value, increments iterator { BinaryTreeNode old = (BinaryTreeNode)todo.pop(); Object result = old.value(); // this node has no unconsidered left children. // if this node has a right child, // push the right child and its leftmost descendants: // now top elt of stack is next node to be visited. if (old.right() != null) { BinaryTreeNode current = old.right(); do { todo.push(current); current = current.left(); } while (current != null); } return result; } }
public Object nextElement() // pre: hasMoreElements(); // post: returns current value, increments iterator { BinaryTreeNode current = (BinaryTreeNode)q.dequeue(); Object result = current.value(); if (current.left() != null) q.enqueue(current.left()); if (current.right() != null) q.enqueue(current.right()); return result; }Note similarity to preorder traversal!