CS 261 Lab #9

March 28th

Goals:

Plan B: Instead of an exam today, you'll get a chance to practice working with trees.

Partners

In each lab this semester you will work with a randomly assigned partner. (I'll have Zoom randomly set up breakout rooms.) Please be kind in your interactions with your partners! Keep in mind that students in this class have a range of previous programming experience, and that some have been college students for longer than others. We're all in this together, and you have something to learn from your partner, no matter who they are or what their previous experiences have been. I expect that group members will collaborate and work together on each step of the lab.

Building and Inspecting Trees

  1. Take a moment to introduce yourself to your partner(s). After social pleasantries are complete, pick one member of the team to be the "typer". They'll share their screen while editing the lab code in BlueJ. (I think BlueJ works better for these interactions than Eclipse. It's easier to see when sharing screens, and makes it easier to quickly test individual methods than Eclipse does.) Group members should contribute equally while working through the problems below and discuss all code to be written, though only the "typer" will be able to edit code. Resist the temptation to have both members work simultaneously in BlueJ — you're much more likely to "drift apart" over the course of the lab if you do so. The goal here is to have a partner who's engaged on exactly the same step of the lab as you are.
  2. Start by downloading the lab project, extract its contents, and open it in BlueJ. The project contains the book's BinaryTree code, and a separate class where you'll do most of your work. But first, let's take a tour of the BinaryTree class...
  3. The BinaryTree class defines an inner class that is uses as a building block when constructing trees, just like SinglyLinkedList did. Go find the inner class. What is it called? What fields does it store? What methods does it support? How is this different from the Node class in SinglyLinkedList?
  4. Now that you've got a grasp on the inner class, take a look at the details of the BinaryTree class itself. What fields does it use? What methods does it support? Are they all public, or are there some private or protected "helpers" like getNode in the list class?
  5. Think about the following questions now that you've finished your tour of the code: How would the user of BinaryTree create a new, empty tree? How would they add a new value to the tree? How would the user access a data value in a tree? (In other words, what's the equivalent of .get() on trees?)
  6. Look at the createTree method in the TreeCode class. It creates and returns a new binary tree containing five strings. Your task is to create a diagram showing the structure of that tree. Start by right-clicking on the TreeCode class and selecting the createTree method. You should get a dialog box like the one shown below:

    Click on the "Get" button, give the result a name, and it will be stored on your object workbench. Here's what mine looked like after giving the result the name "bt":

    Double-click on the red object to inspect its state. You should see the root field shown as an arrow. Double click that to see where it leads. Each time you discover a new object (a new red window pops up), double-click on all of its object references, and repeat the process until there are no more arrows to follow. Drag the red windows around to approximate the structure of the tree they're representing. (For example, if one object points to others, arrange the "children" below their "parent".) The goal here is to gain confidence in your understanding of how trees are represented in terms of objects and object references, and make sure you have the ability to interactively explore any tree structures you come across in the future.
  7. Go back to the code in the createTree method and see if you can figure out how the code there built the tree you just explored. What are each of those constructor calls doing? To demonstrate your mastery of tree code, rearrange the code in createTree (or create a new version of it) such that it creates the tree shown below. Verify that it really did build a tree with that structure by using the exploratory approach you used in the previous step.

Traversing Trees

  1. In the TreeCode class, finish the definition of rightmost. It should take a BinaryTree of integers, and return the value from the rightmost node in the tree. (That is, keep going right until there's no right subtree, and return the value from last node you hit.) Note that the rightmost node might not be a leaf node. You can write this one with a loop if you prefer, but it's better practice to write it recursively. Below, the interactions show the method being called on trees created by the smallTree() and bigTree() methods.

    > TreeCode.rightmost(TreeCode.smallTree())
       70   (int)
    > TreeCode.rightmost(TreeCode.bigTree())
       130   (int)
    
  2. The last method only had to explore one "path" through the tree. Now finish the definition of countNodes, which takes a binary tree and counts the number of nodes it contains. This will require exploring all paths through the tree, and should be written recursively. Keep in mind that you can assume that countNodes works properly as you go about implementing it. How could you make use of one or more recursive calls to countNodes to help process the subtrees? How would you come up with the complete total after making those recursive calls?

    > TreeCode.countNodes(TreeCode.smallTree())
       7   (int)
    > TreeCode.countNodes(TreeCode.bigTree())
       13   (int)
    
  3. Next, finish the definition of printTree. It should print the contents of the tree out on a single line, using parenthesis to help illustrate the structure of the tree. These interactions show sample outputs:

    > TreeCode.printTree(TreeCode.smallTree());
     ( ( (10) 20 (30) ) 40 ( (50) 60 (70) ) ) 
    > TreeCode.printTree(TreeCode.bigTree());
     ( ( ( (10) 20 (30) ) 40 ( (50) 60) ) 70 ( ( (80) 90 (100) ) 110 ( (120) 130) ) ) 
    
  4. Finish the definition of contains. It looks through the nodes in a binary tree to see if the tree contains a specific item, but does not assume that the items in the tree are in any particular order. (In other words, you potentially need to check each and every node's value for a match.) Recursion will be your friend once again here.

    TreeCode.contains(TreeCode.bigTree(), 30)
       true   (boolean)
    TreeCode.contains(TreeCode.bigTree(), 110)
       true   (boolean)
    TreeCode.contains(TreeCode.bigTree(), 70)
       true   (boolean)
    TreeCode.contains(TreeCode.bigTree(), 55)
       false   (boolean)
    

Extras

If you've got extra time, consider trying the following:
  1. Write a method that returns true if a tree is full. That is, each node either has two children, or is a leaf.
  2. Write a method that adds a new value to a tree of integers. It should take a binary tree and an integer, and returns a new tree that looks just like the original but contains the new value as a leaf somewhere.
  3. Write a method that removes a specified item from a binary tree. This is harder, since you have to think about how to "fill the gap" that the removal creates in the tree.


Brad Richards, 2023