Lab 5

Straight Up Binary Trees
Due by 8pm, Sunday 7 Oct 2012

In this lab you will implement your very own recursive binary tree and associated methods. The purpose of this lab is to:

I've provided a GUI-based Java application which you can use to test your methods. You should get the starter code from lab05.jar. As with previous labs, once you unjar this file, you should start a new project in eclipse from the directory storing the unjarred files. The java files you should see are:

As noted above, all files are complete, except for TreeLoader.java, which contains only a stub of the loadTreeFromFile method, and TreeMethods.java, which contains only stub routines for several methods with operate on binary trees. Don't worry about what these files do, for now, we will explain them soon enogh.


Part 1 - Getting the GUI up and running

The first part of your lab is to get the GUI up and running. The GUI is going to help you test the methods you write, so it's best to have it working right from the start. In order to do so, you are going to have to get a bare-bones BinaryTree, EmptyTree, and ConsTree.

BinaryTree

As you may recall from class, a binary tree is either:

  1. empty, or
  2. a root node of data with a left and right (sub)tree.

Therefore, you should create three classes:

  1. an abstract class BinaryTree<T> to represent any binary tree,
  2. a subclass EmptyTree<T> of BinaryTree<T> to represent any empty binary tree, and
  3. a subclass ConsTree<T> of BinaryTree<T> to represent any non-empty binary tree. This class should contain fields appropriate for a non-empty binary tree.
In this way, any binary tree you create will either be Empty or Cons.

Constructors

public EmptyTree( )
does nothing.
public ConsTree(T data, BinaryTree<T> left, BinaryTree<T> right)
sets this tree's class variables appropriately.
public ConsTree(T data)
calls the 3-argument constructor with left and right as new EmptyTree().
(you call another constructor of the same class with the this keyword)

TreeLoader and TreeMethodApplication

At this point, all of the compile errors should be gone from all files. This means you should be able to run two of the given programs to make sure they are working. They are pretty boring right now, but let's just give them a shot.

The first program is TreeLoader, which has as its main method a simple testing program. It accepts a filename as a command-line argument, loads a binary tree from the file, and then displays it in the same format that is used for the TreeMethodApplication. If you look at the TreeLoader class, you can see that the loadTreeFromFile method, by default, just returns an EmptyTree. This is because you will be filling in this method momentarily. The effect of this default behaviour is that the program just prints out an Empty tree, every time, irrespective of the file you give it. And since we haven't overridden the toString methods in our tree classes to do anything interesting, a call to this main method should just print out the address of the empty tree that we construct. E.g.

% java TreeLoader tree1.txt
EmptyTree@2c6f7ce9

Try this out in the console and check that it works. Obviously you won't get the same address as me for your EmptyTree, but it should be something similar.

The second program is TreeMethodApplication. Launch it now with the following command:

% java TreeMethodApplication

As you can hopefully see, this application's GUI frame has two text areas for output, and a group of buttons for user commands. The "load file" button allows the user to load a binary tree from a disk file. The jar file also contains some sample files you can use for testing, in the format described below.

When a file is loaded, a binary tree object is created, and the tree is displayed in the left area of the frame, using the loadTreeFromFile method. As mentioned above, right now this is just constructing an empty tree, but you will later implement it so that the tree from the file is displayed in inorder sequence, with the data from each node on a separate line. Each node is indented one space from its parent, so the structure of the tree is visible. (Basically, turn your head 90 degrees counterclockwise and you should see the tree as you might draw it on paper without edges.)

The area on the right side of the frame is used for the output of commands. The output may be a single number, as in the "height" method, or it may be another tree, as in the "mirror" method. You can press these buttons and they will display some (incorrect) default behaviour we put in the TreeMethods class.


Part 2 - Loading a Binary Tree from a File

Your task for this part is to implement the loadTreeFromFile method in the TreeLoader class. We'll walk you through it.

The test files for the GUI list the nodes of the tree in postorder sequence, one node per line. Each line contains a string, which is the data value stored in the node, and two tag bits. The first tag bit indicates whether or not this node has a left child or not. (1=oui,0=non). The second tag bit indicates whether the node has a right child or not.

For example, the tree

        34
     /      \
   74       83
     \     /  \
     32  63    18
         /
        29

is represented as

    32 0 0
    74 0 1
    29 0 0
    63 1 0
    18 0 0
    83 1 1
    34 1 1

The information in the file is sufficient to uniquely determine a binary tree. The tree can be constructed from the file using the following algorithm:


create an empty stack of binary trees (i.e. Stack<BinaryTree<String>>)
while there is another line of input in the file
    read a line (containing data and two tags)
    if right tag is 1, pop an element from the stack and call it right
    if left tag is 1, pop an element from the stack and call it left
    create a new binary tree with data as its root and
           left and right as its subtrees (if they exist)
    push the new tree onto your stack
when you exit the while loop, the stack should contain one element
this element is the the tree represented by the file data (so return it!)

NOTE: the input file is left then right, but the algorithm
uses the right value first, then the left. Not a typo.

You need to implement this algorithm in the loadTreeFromFile method, with the following method signature:

    public BinaryTree<String> loadTreeFromFile(String filename)

The method should create a Scanner object to read strings from the file whose name is given as an argument. It should create a Stack of BinaryTree<String> objects to use as a work area. You can use the Scanner and Stack classes from java.util (unless you have über-confidence in your own MyStack class.)

NOTE: Use Java generics in your code when declaring and instantiating a tree or stack. (Use BinaryTree<String> and Stack<BinaryTree<String>>.) Your code should not generate any warning messages when compiled.

Test out your program, using either or both of TreeLoader and TreeMethodApplication. Hopefully you don't get any runtime exceptions! But you may notice that your tree is displaying itself as a pointer ...

Therefore you need to write toString methods in both the EmptyTree and ConsTree classes. (There is no need to add the no-parameter abstract toString declaration in the BinaryTree class, because all Objects have toString by default. You'll have to add the one-parameter version I suggest below, though.) I'm going to suggest a way to do this that produces the tree, rotated to the left by 90 degrees. That is, you need to turn your head so that the left side of the screen is the "top" of the tree. The example tree from above would look like the following:

    18
  /
  83
  \
    63
    \
      29
/
34
\
    32
  /
  74

It's not art, people, but it works. So, how can you do this yourself? You may want to think about it a bit, because it's a cool puzzle. You need to use a helper toString method that takes an indentation as a parameter. Each recursive call to the subtrees increases the indentation by some number of spaces. Anyway, here is the methods that you should add to your ConsTree class:

    public String toString( String indent ) {
	return right.toString( indent + "   " ) + "\n" + 
	       indent + "/\n" + 
	       indent + data + "\n" + 
	       indent + "\\\n" + 
	       left.toString( indent + "   ");
    }
    public String toString() {
	return toString("");
    }
    

For the EmptyTree class, things are simpler, as usual:

    public String toString( String indent ) {
	return "";
    }
    public String toString() {
	return toString("");
    }
    

Feel free to mess around with these a bit if you can make improvements! And add the method header of this method to your BinaryTree class, because this isn't your usual toString method (it has a different parameter list.)



Part 3 - Implementing Your Binary Tree

Public Methods

Now, for each of the methods listed below, do the following:

  1. add it as an abstract method to the BinaryTree class.
  2. implement the method in the EmptyTree class.
  3. implement the method in the ConsTree class.
  4. implement the associated method in the TreeMethods class. (Usually by calling the tree method directly in one line.)
  5. test the added methods using the GUI application and given test files.

Note that you should not need any loops in any of these methods.

For example, for the height method below, you will:

  1. add the following to BinaryTree:
        public abstract int height( );
    
  2. add the following to EmptyTree:
        public int height( ) { return -1; }
    
  3. add the following to ConsTree:
        public int height( ) { 
    	return 1 + Math.max(left.height(),right.height()); 
        }
    
  4. create some binary trees and check their heights.

Here are the list of methods:

int height( )
return the height of the tree. We'll define the empty tree to have a height of -1, and a single node to have height 0.
int nodeCount( )
return the number of nodes in the tree.
int leafCount( )
return the number of leaves in the tree.
int levelCount(int level)
return the number of nodes at a given level in the tree. Level 0 is the level of the root node, level 1 are the (up to 2) children of the root, and so on.
BinaryTree<T> mirrorImage()
return a new tree which looks like the mirror image of the given tree (mirrored around the root node, so left becomes right and right become left.) As in the clone() method for Collections, the new tree should contain all new nodes, not sharing any with the original tree. However, the data objects in the tree should be the same objects as those in the original tree -- those objects should not be cloned. (This is also known as a shallow copy.)
int weightBalanceFactor()
return the "weight-balance factor" of the tree. The weight-balance factor of a binary tree is a measure of how well-balanced it is; that is, how evenly its nodes are distributed between the left and right subtrees of each node. Define the weight-balance factor of binary tree as follows: it is the maximum value of the absolute value of (number of nodes in left subtree - number of nodes in right subtree) for all nodes in the tree. Whew! That takes some parsing!

The remaining methods assume that the strings stored in the binary tree are actually integers. Your code will have to convert them to ints using the Integer.parseInt() method. (If you apply one of these methods to a tree containing non-integer data, an exception will be thrown and a message displayed in the right panel of the application window. Test files 6-10 contain integer data.)

int nodeSum()
Return the sum of the data values in the tree.
void doubles()
Double the integer value in every node of the tree. (Note that you may only be able to do this once; doubling twice often gets you some sort of casting error, strangely.)
int maxPathSum()
Define a "path sum" in a tree to be the sum of the data values in a path from the root to a leaf (including the data values in both the root and the leaf). This method returns the maximum of all path sums in the tree. (Note that the maximum root-leaf path could be negative, that's okay, provided it's the max!)

Finally, you'll create some methods that will traverse the nodes in the tree.

String preOrder()
Perform a preorder traversal of the nodes, returning a String representing the order in which you "visit" the nodes. Embed a newline "\n" after each data element so that the output looks like:
E
D
C
A
B
String postOrder()
Perform a postorder traversal of the nodes, returning a String representing the order in which you "visit" the nodes. Embed a newline "\n" after each data element so that the output looks like:
A
B
C
D
E
String inOrder()
Perform an inorder traversal of the nodes, returning a String representing the order in which you "visit" the nodes. Embed a newline "\n" after each data element so that the output looks like:
E
A
C
B
D

Testing

The provided source code contains 10 test files from which you can read binary trees.


handin

Look through your programs and make sure you've included your name at the top of all of them.

README file

You need to create a file called "README" that contains the following information:

  1. Your name
  2. Any known problems or assumptions made in your classes or program
  3. Anything you've implemented for extra credit.

Honor code

If you adhered to the honor code in this assignment, add the following statement to your README file:

I have adhered to the Honor Code in this assignment.

handin

You now just need to electronically handin all your files. Assignment is 5.

Don't forget to run lshand and verify that things were submitted.