// The Science of Computing OrderedTree class.
// History:
// August 2003 -- Created by Greg Scragg.
// October 2003 -- updates to several methods by GWS
// February 2004 -- updated references and variable names to correspond to text
// changes
// April 2004 -- Code cleaned up and documentation expanded by Doug Baldwin.
package geneseo.cs.sc;
import java.io.*;
/**
* Represents ordered binary trees containing arbitrary objects. These
* trees conform to a recursive definition of "tree" as a structure that
* is either
*
* - Empty, or
* - An object (the root) associated with two smaller trees
* (the left and right subtrees)
*
* These trees are "ordered" in the sense that the objects
* in the trees are required to support a "less than" relation (Java
* interface Comparable
), and objects are placed in a tree
* in such a way that all object's in the tree's left subtree are less
* than the root object, and all objects in the right subtree are
* greater than or equal to the root object.
* The messages these trees handle are generally standard ones for
* any ordered tree class. Some are destructive, and therefore do not require
* assignments within client code when modifying trees.
* This class was created as a support tool for the text
* Algorithms & Data Structures: The Science of Computing
* by Doug Baldwin and Greg Scragg. All references herein to "the text"
* refer to that book. Trees are described in Chapter 13 of the text.
* Some of the methods of this class are ones "left to an exercise"
* in the text.
* @see java.lang.Comparable
*/
public class OrderedTree implements Serializable {
private Comparable root;
private OrderedTree left;
private OrderedTree right;
/**
* Initialize a new ordered tree to be empty. For example
* OrderedTree tree = new OrderedTree();
*/
public OrderedTree() {
root = null;
left = null;
right = null;
}
/**
* Create a new tree that is an instance of the same class as this tree.
* This message is used, for example, when adding an item to a tree requires
* creating new subtrees for that tree. The new subtrees should be instances
* of the same class as the tree itself, even if that tree is an instance of
* a subclass of OrderedTree
. Every subclass of
* OrderedTree
must therefore provide a method for handling this
* message that returns a new instance of that subclass. Clients are unlikely
* to ever invoke this method directly, but it is essential to the correct
* functioning of other methods defined for trees.
*/
public OrderedTree makeNewTree() {
return new OrderedTree();
}
// Access functions
/**
* Extracts the root of a tree. The tree itself remains unchanged.
* For example
* Comparable r = someTree.getRoot();
* @return The value at the root of the tree.
*/
public Comparable getRoot() {
return root;
}
/**
* Extracts the left subtree of a tree. The tree itself remains unchanged.
* For example
* OrderedTree subtree = someTree.getLeft();
* @return The left subtree of the tree.
*/
public OrderedTree getLeft() {
return left;
}
/**
* Extracts the right subtree of a tree. The tree itself remains unchanged.
* For example
* OrderedTree subtree = someTree.getRight();
* @return The right subtree of the tree
*/
public OrderedTree getRight() {
return right;
}
/**
* Replaces the root value in a tree. For example
* someTree.setRoot( "new" );
* Most clients should not use this message, but methods in
* subclasses that need to construct trees in unusual ways may
* find it helpful.
* @param newValue The new root value.
*/
protected void setRoot( Comparable newValue ) {
root = newValue;
}
/**
* Replaces the left subtree of a tree. For example
* someTree.setLeft( otherTree );
* Most clients should not use this message, but methods in
* subclasses that need to construct trees in unusual ways may
* find it helpful.
* @param newLeft The tree that should replace the left subtree.
*/
protected void setLeft( OrderedTree newLeft ) {
left = newLeft;
}
/**
* Replaces the right subtree of a tree. For example
* someTree.setRight( otherTree );
* Most clients should not use this message, but methods in
* subclasses that need to construct trees in unusual ways may
* find it helpful.
* @param newRight The tree that should replace the right subtree.
*/
protected void setRight( OrderedTree newRight ) {
right = newRight;
}
/**
* Determines whether a tree is empty. For example
* if ( someTree.isEmpty() ) ...
* @return True if the tree is empty, false otherwise.
*/
public boolean isEmpty() {
return (root == null && left == null && right == null);
}
/**
* Determines whether a tree contains a value. The value is considered to
* be in the tree if any subtree's root is equal to the value according
* to the equals
message. For example
* if ( someTree.find("Hello") ) ...
* @param target The value to seek.
* @return True if the value is contained somewhere within the tree, false
* otherwise.
*/
public boolean find (Comparable target) {
if (this.isEmpty())
return false;
else if(root.equals(target))
return true;
else if (root.compareTo(target) > 0)
return left.find(target);
else
return right.find(target);
}
/**
* Removes one occurrence of a value from a tree. Searches the tree
* for a node containing the value, using the equals
* message to determine whether the target value matches the node's
* value, and removes that occurrence. This message alters the tree.
* For example
* someTree.delete( "Goodbye" );
* @param target The value to remove from the tree.
*/
public void delete (Comparable target) {
if (!this.isEmpty()) {
if(root.equals(target))
this.cutAndRaise();
else if (root.compareTo(target) > 0)
left.delete(target);
else
right.delete(target);
}
}
/**
* Replaces a tree with a leaf -- i.e., replaces any value at the root
* of the tree, and makes both subtrees empty. For example
* someTree.insertValue( "replacement" );
* @param newValue The new root value.
*/
public void insertValue (Comparable newValue) {
root = newValue;
left = this.makeNewTree();
right = this.makeNewTree();
}
/**
* Adds a new value to the tree in a leaf position that maintains the tree's
* order. For example
* someTree.addValue( "new" );
* @param newValue The value to insert into the tree.
*/
public void addNode (Comparable newValue) {
if (this.isEmpty()) {
this.insertValue(newValue);
}
else if (root.compareTo(newValue) > 0) {
left.addNode(newValue);
}
else {
right.addNode(newValue);
}
}
/**
* Removes a tree's root node. Replaces the root, if necessary, with a value
* from one of the subtrees, so that all values originally in the tree except
* the root remain in the tree, and the tree remains ordered. For example
* someTree.cutAndRaise();
*/
// See Section 13.5 of the text for a full explanation of this algorithm.
protected void cutAndRaise() {
if(this.isEmpty()) {
root = null;
right = null;
left = null;
}
else if (left.isEmpty()) {
root = right.root;
left = right.left;
right = right.right;
}
else if (right.isEmpty()) {
root = left.root;
right = left.right;
left = left.left;
}
else if (left.right.isEmpty()) {
root = left.root;
left = left.left;
}
else {
OrderedTree secondLargest = this;
OrderedTree largest = left;
while (!largest.right.isEmpty()) {
secondLargest = largest;
largest = largest.right;
}
root = largest.root;
secondLargest.right = largest.left;
largest.left = null;
}
}
/**
* Prints the contents of a tree in order. For example
* someTree.printTree();
*/
public void printTree() {
if (!this.isEmpty()) {
left.printTree();
System.out.println(root);
right.printTree();
}
}
/**
* Prints the contents of a tree in preorder (i.e., roots before the
* contents of their subtrees). For example
* someTree.printTreePreOrder();
*/
public void printTreePreOrder() {
if(!this.isEmpty()) {
System.out.println(root);
left.printTreePreOrder();
right.printTreePreOrder();
}
}
/**
* Prints the contents of a tree in postorder (i.e., subtrees before
* roots). For example
* someTree.printTreePostOrder();
*/
public void printTreePostOrder() {
if(!this.isEmpty()) {
left.printTreePostOrder();
right.printTreePostOrder();
System.out.println(root);
}
}
/**
* Writes a tree to a file, replacing any previous data in that file.
* For example
* someTree.save( "TreeFile" );
* @param fileName The name of the file to write the tree to.
*/
// This method opens a stream to the file, writes this tree to the stream, and
// closes the stream. If anything goes wrong while doing these things, this method
// prints an error message but doesn't signal its client that anything has failed.
public void save( String fileName ) {
try {
ObjectOutputStream out = new ObjectOutputStream( new FileOutputStream( fileName ) );
out.writeObject( this );
out.close();
}
catch ( Exception error ) {
System.err.println( "Unable to save binary tree: " + error );
}
}
/**
* Restores a binary tree from a file. For example
* someTree.restore( "TreeFile" );
* After restoring a tree, the contents of the tree are replaced by
* the contents of the file, if possible. If for some reason the file
* couldn't be read, the contents of the tree are unchanged.
* @param fileName The name of the file to restore the tree from.
*/
// This opens an input stream and reads a tree from that stream. If the read succeeds,
// this then copies the new tree's members into the tree executing this method, thus
// changing that tree into the one read. If anything goes wrong in reading, this prints
// an error message, but doesn't let client code know there was an error.
public void restore( String fileName ) {
try {
ObjectInputStream in = new ObjectInputStream( new FileInputStream( fileName ) );
OrderedTree retrieved = (OrderedTree) in.readObject();
in.close();
root = retrieved.root;
left = retrieved.left;
right = retrieved.right;
}
catch ( Exception error ) {
System.err.println( "Unable to restore a binary tree: " + error );
}
}
/**
* Generates a string representation of a tree. For example
*
String text = someTree.toString();
* @return The string representation of the tree. An empty tree is represented
* as "< >", and a non-empty tree as a bracket ("<"),
* the left subtree, the root value, the right subtree, and finally a
* closing bracket (">").
*/
// This generates the string recursively, using the insight that an empty
// binary tree can be represented by the string "< >", while a non-empty
// one can be represented by the left subtree, the root, and the right
// subtree, all enclosed in "<" and ">".
public String toString() {
if ( this.isEmpty() ) {
return "< >";
}
else {
return "<" + left + " " + root + " " + right + ">";
}
}
}