Laboratory Exercises For Computer Science 153

An Introduction to Trees in Java

An Introduction to Trees in Java

Goals: This lab introduces the concept of a tree data structure, describes a binary search tree as a specific type of tree, and considers how such a tree structure might be implemented in Java.

Some Definitions

A general tree is defined recursively as follows:

Terminology: I is called the root or root node, and each Ti is called a subtree of the tree. Nodes that have only null subtrees are called leaves or leaf nodes.

Since this definition is recursive, it may be applied multiple times to construct more complex trees, such as the one shown below:

A Simple Tree

In this example, e, k, l, m, n, h, o, p, and q are leaves (or trees with null subtrees); a is the root of the overall tree. f is the root of a tree with k and l as subtrees, etc. Similarly, b is the root of a tree with two subtrees, one containing e and one containing f, k, and l.

Binary Search Trees

A binary search tree or BST is a special type of tree, in which

A schematic view of such a tree follows:

A binary search tree

  1. Which of the following trees is(are) a binary search tree(s)?

Several trees

Implementing Binary Search Trees in Java

The implementation of a binary search tree in Java follows a similar approach to our implementation of lists. First, we consider each data element in a tree to be an object of a TreeNode class. Then we define a BSTree class which combines the nodes into an overall structure.

A TreeNode Class

For a binary search tree (or any binary tree, for that matter), each node will contain data, and each node will have a left and right variable to designate the relevant subtrees -- although either or both of the subtrees could be null. Thus, a TreeNode should have variables data, left, and right, together with constructors and methods to access and modify these fields.

For the most part, the code for TreeNode can be parallel to ListNode from the lab on lists. However, binary search trees require that nodes conform to a special ordering -- all nodes in a left subtree must have data smaller than in a root node, while all nodes in a right subtree must have larger data. In order to maintain this property throughout the tree, we must be able to test the relative ordering of data. That is, we need a comesBefore method as well as an equals. Unfortunately, the Object class contains only equals. Hence, we cannot allow elements in a tree to be as general as an Object. Instead, for illustration here, we use the Entry class from the school directory example in the lab on generalization.

Program TreeNode.java shows a typical declaration for such a node class.

A Binary Search Tree Class

Before implementing any class, we must identify the appropriate operations. As with the school directory example in the lab on generalization, we consider the following basic operations:

In the following discussion, we outline the approach for several of these operations. As with our discussion of lists previously, we use both iterative and recursive algorithms for illustration -- although either approach could be used for many of these operations.

For our implementation, we also need an image of how a BSTree class will package tree information. Again, we use the discussion of lists as a model -- considering various tree nodes to point to their subtrees within the BSTree. Thus, the BSTree class itself only need specify the initial node or root. Thus, the first binary search tree identified in this lab might be annotated as follows as a BSTree object:

A BSTree Object

A BSTree Class, with Commentary

Program BSTree.java implements a binary search tree, including several methods already identified. This program also contains the same testing sequence used for the SchoolDirectory program involving lists.

For the next part of this lab, you should review various elements of the code in conjunction with the following commentary on the various methods.

Construction: As with lists, an initial binary search tree will be empty. This may be implemented by setting the root variable to null.

lookup: Searching in a binary search tree proceeds downward from the root. Following the recursive patterns that are familiar from Scheme, we identify the following cases:

print: A recursive algorithm starts at the root and applies the following steps for each node:

The above sequence of events is called an in-order traversal of a tree. Similarly, printing the data at the node, then the left subtree, and then the right subtree is called a pre-order traversal. Printing the data in the node last gives a post-order traversal.

  1. Copy TreeNode.java and BSTree.java to your account. Compile and run the programs to verify they produce the same results as the SchoolDirectory program described in the lab on generalization.

  2. What can you say about the order of the entries printed by the print procedure? Explain why this sequence is obtained.

insert: For variety, we use an iterative approach to insert entries into a tree. To start, a simple base case checks whether the tree is empty. If so, a new node is generated, initialized with the relevant data, and identified as the new root.

To understand the rest of the insertion process, consider the insertion of the number 153 into the following tree (which repeats the tree given above).

A BSTree Object

To insert 153, we start at the top of the tree. Checking that 153 comes after the value in the root (123), we advance to the right subtree. We now check that 153 comes before 285, so we advance to the left subtree of 285. Again, we compare 153 with value 185, and realize we should move left. Here, however, we discover there are no further nodes. Thus, we create a new node, place 153 in that node, and identify the new node as the left child of 185's node.

In the code for insert, the variable ptr keeps track of where we are as we work downward node-by-node from the root. To test if an item (person for the BSTree program) comes before the value in a node, we use the following sequence:

  1. Suppose a similar insert method was used to build the tree in the above example (with numbers 23, 37, 48, 96, 123, 185, 200, 285, and 309 rather than names and entries).

    1. What data do you think would have to be inserted first into the null tree?
    2. What item or items might have been inserted next?
    3. What flexibility might there be in the order of entering data to get the above tree, and what restrictions might apply?

    Explain your answers.

  2. Given the order of insertions in the main method of BSTree.java, draw a picture of the binary search tree that is produced by that program.

  3. Add an update method to the BSTree class, analogous to the corresponding method for the SchoolDirectory program from the lab on generalization.

    Note: As with the similar problem for the SchoolDirectory, the body of this update method can be just two lines long!

Additional Practice

  1. Write an iterative version of the recursive lookup method.

  2. Use ideas from print to write a printLeaves method, which prints just the leaves within a tree. Here, one can still traverse the full tree -- but printing should occur only if a node has only null left and right subtrees.

  3. Ideas from print also can be used to count the number of (non null) nodes in a tree. Use this approach to write a countNodes method.

Extra Credit:

  1. The height of a tree is the maximum number of levels of nodes within the tree; by convention, the height of a tree with only one node is 0. Thus, the first binary search tree shown in the lab (with root 123) has height 3. Add to the BSTree class a method height which computes the height of a tree.

Work to be turned in:


This document is available on the World Wide Web as

http://www.math.grin.edu/~walker/courses/153.s90/lab-intro-trees.html

created April 18, 1998
last revised May 5, 2000