Lab 13: Two nifty binary tree applications

This goal of this lab is to practice writing operations for binary trees, as an application of and variation on linked lists.

1. Set up

As usual, make a new directory for this lab and cd into it. Copy all the files from a subdirectory for this lab.

cp /homeemp/tvandrun/pub/235/lab13/* .

2. Part I: Breadth First Traversal

A. Introduction

In class we looked at recursive methods to perform a depth first traversal on a tree, with the variations of pre-, in-, and post-order visiting of the nodes. An alternative to all these is breadth first traversal. For example, given a tree

                         1
                       /   \
                      2     3
                     / \   / \
                    4   5 6   7

a breadth first traversal would go horizontally (by rows), and visit the nodes (or print the data) 1, 2, 3, 4, 5, 6, 7.

If you think about this problem, you'll see that there is not clear way to do this recursively. After you visit 1 and 2, you still need to visit 2's children 4 and 5, but not right away; first you need to visit 3. To do this, you need to use a data structure called a queue.

A queue is a container for data (a data structure) characterized by the ability to add elements at one "end" and retrieve them at the other "end", in the same order in which they were entered. For example, if you added 3, then added 7, and then added 5, then if you tried to retrieve an element, you would get 3. If you then added 8 and 4, and then retrieved elements until the queue was empty, you would get 7, 5, 8, and 4.

B. Implementing a queue

The interface Queue defines the operations of a queue: add an element to the back, remove (and return) an element from the front, and test if the queue is empty. Write a class that implements this interface; although a queue class could use an array as its internal implementation, it is much easier to make the queue a variation on a linked list. The class Node is provided for you for this; that Node class is exactly like the one we've used before except that the datum is of a different type; this time we do not want to store ints in the queue, but rather TreeNodes. It may be a little confusing at first to deal with the fact that you have two variety of nodes-- Nodes for the list/queue, and TreeNodes for the binary trees, and that the Nodes contain TreeNodes as their datum.

Compile your class which implements Queue. You will test it in the next section.

C. Performing a breadth first traversal

Now finish the main method in the file BreadthFirst.java. Write code which will traverse the tree and print each datum as it visits the nodes. Do this iteratively, using your queue. Think of the queue as your list of nodes that you know need to be visited at some point.

3. Part II: Expression trees

A. Introduction

One use for trees is in the grammatical (or syntactical) analysis (or "parsing") of languages, both human languages and programming languages. As a simple example, the following grammar describes the language of fully parenthesized arithmetic expressions:

expression --> integer | ( expression op expression)

op --> + | - | / | *

This means "An expression is either a single integer, or it is a left parenthesis, a (sub-)expression, an operator, another (sub-)expression, and a right parenthesis. An operator is a plus, a minu, a slash, or a star." According to this grammar, the following are expressions:

  • 15
  • (42 - 17)
  • ((18 * 10) / 147)
  • ((14 * 91) + (22 - 17))

    These can be represented as trees. (We can ignore the parentheses now, since the information they give about grouping is contained in the structure of the tree itself.)

    15      -        /           +
           / \      / \        /   \
         42   17   *   147    *     -
                  / \        / \   / \
                18   10    14  91 22  17
    

    Expression can be modelled by a node-like interface with two implementing classes: one for leaf nodes containing integers, and one for nodes containing an operator and having two children. Evaluation of the expressions can be performed with a depth-first post-order traversal of the tree.

    B. ExprNode classes

    Your first task is to design classes that model the two kinds of nodes, implementing the ExprNode interface. These nodes will have to have evaluate() methods, but we'll deal with that later.

    So, write two classes, one for leaf/integer nodes, the other for non-leaf/operation nodes. What instance variables will they have?

    C. Building expression trees

    Next, you will need to write code to build an expression tree given a String containing a fully-parenthesize expression. The driver program Interpreter is intended to work so that the user can write a expression in the commandline, and the program will build an appropriate expression tree, and then evaluate the expression using the tree. Implement this in the main method of Interpreter and the the constructors of the classes you wrote in part B.

    One thing you will find useful is a method I have written and included in the code you copied. In the class ExprStringSlicer, the static method slice() takes a String, assumed to contain an expression in our grammar. It will return an array of Strings: if the String passed to it is just an integer, then it will return an array with exactly one element, a String version of that integer; if it is passed a String with a parenthesized operation over subexpressions, it will return an array with three elements, the first subexpression, the operator, and the second subexpression. For example, given "5", it will return { "5" }. Given "((14 * 91) + (22 - 17))", it will return { "(14 * 91)", "+", "(22 - 17")}.

    When you test out your program, you will need to put quotes around the string you give to Interpreter. For example, you could run the program with

    java Interpreter "((14 * 91) + (22 - 17))"
    

    The quotes tell Java to treat ((14 * 91) + (22 - 17)) as a single String (it will be the zeroth item in args). Otherwise every space would be assumed to begin and end a separate String; furthermore, the parentheses would reall mess things up.

    D. Evaluating expression trees

    Finally, the interpreter calls the evaluate() method on the trees you have built. Write the evaluate methods so that they return the integer value of the given expression.

    6. Turn in

    Create the script file as before (cat--only files you've written or changed, rm, compile, and run)

     a2ps -P sp (the name of the script file)
    

    Thomas VanDrunen
    Last modified: Mon Apr 2 15:43:20 CDT 2007