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?