// 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 * *

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 + ">"; } } }