// 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
 *   <ul>
 *     <li>Empty, or</li>
 *     <li>An object (the root) associated with two smaller trees
 *       (the left and right subtrees)</li>
 *   </ul>
 * <p>These trees are &quot;ordered&quot; in the sense that the objects
 * in the trees are required to support a &quot;less than&quot; relation (Java
 * interface <code>Comparable</code>), 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.</p>
 * <p>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.</p>
 * <p>This class was created as a support tool for the text
 * <cite>Algorithms &amp; Data Structures: The Science of Computing</cite>
 * by Doug Baldwin and Greg Scragg. All references herein to &quot;the text&quot;
 * refer to that book. Trees are described in Chapter 13 of the text.
 * Some of the methods of this class are ones &quot;left to an exercise&quot;
 * in the text.</p>
 * @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
     * <p><code>OrderedTree tree = new OrderedTree();</code></p>    
     */
    
    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 <code>OrderedTree</code>. Every subclass of
     * <code>OrderedTree</code> 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
     * <p><code>Comparable r = someTree.getRoot();</code></p>
     * @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
     * <p><code>OrderedTree subtree = someTree.getLeft();</code></p>
     * @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
     * <p><code>OrderedTree subtree = someTree.getRight();</code></p>
     * @return The right subtree of the tree
     */
    
    public OrderedTree getRight() {
        return right;
    }
    
    
    
    
    /**
     * Replaces the root value in a tree. For example
     * <p><code>someTree.setRoot( "new" );</code></p>
     * <p>Most clients should not use this message, but methods in
     * subclasses that need to construct trees in unusual ways may
     * find it helpful.</p>
     * @param newValue The new root value.
     */
     
    protected void setRoot( Comparable newValue ) {
        root = newValue;
    }
    
    
    /**
     * Replaces the left subtree of a tree. For example
     * <p><code>someTree.setLeft( otherTree );</code></p>
     * <p>Most clients should not use this message, but methods in
     * subclasses that need to construct trees in unusual ways may
     * find it helpful.</p>
     * @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
     * <p><code>someTree.setRight( otherTree );</code></p>
     * <p>Most clients should not use this message, but methods in
     * subclasses that need to construct trees in unusual ways may
     * find it helpful.</p>
     * @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
     * <p><code>if ( someTree.isEmpty() ) ...</code></p>
     * @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 <code>equals</code> message. For example
     * <p><code>if ( someTree.find("Hello") ) ...</code></p>
     * @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 <code>equals</code>
     * message to determine whether the target value matches the node's
     * value, and removes that occurrence. This message alters the tree.
     * For example
     * <p><code>someTree.delete( "Goodbye" );</code></p>
     * @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
     * <p><code>someTree.insertValue( "replacement" );</code></p>
     * @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
     * <p><code>someTree.addValue( "new" );</code></p>
     * @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
     * <p><code>someTree.cutAndRaise();</code></p>    
     */
    
    // 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
     * <p><code>someTree.printTree();</code></p>
     */
    
    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
     * <p><code>someTree.printTreePreOrder();</code></p>
     */
    
    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
     * <p><code>someTree.printTreePostOrder();</code></p>
     */
    
    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
     * <p><code>someTree.save( "TreeFile" );</code></p>
     * @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
     * <p><code>someTree.restore( "TreeFile" );</code></p>
     * <p>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
     * <p><code>String text = someTree.toString();</code></p>
     * @return The string representation of the tree. An empty tree is represented
     *  as &quot;< >&quot;, and a non-empty tree as a bracket (&quot;<&quot;),
     *  the left subtree, the root value, the right subtree, and finally a
     *  closing bracket (&quot;>&quot;).
     */
    
    // 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 + ">";
        }
    } 
    
}