CSCI 151 - Lab 6 AVL Trees

Due by 6 PM on Sunday, April 24

Overview

AVL Trees rebalance themselves each time you insert a new item into them. Because they are structured as Binary Search Trees inserts, removals and searches all walk down a path from the root to leaves. Because they are balanced, all of these operations run in worst-case time O( log n ). In this lab you will implement inserts and searches for AVL trees.

Part 1. Understanding the structures

This lab has a file of starter code. As usual, download Lab 6.zip, expand it into your cs151 folder, change the project name to include your name, and start an Eclipse project with this code.

You should find two files:

As with many of our other data structures, the MyTreeMap class contains a nested Node class. Node has fields key, value, leftChild, rightChild and height. To avoid constantly checking to see if a node is null before looking at its fields, we set up the tree so that leaf nodes contain empty nodes rather than nulls for their children. We don't make a separate EmptyNode class; we just identify an empty node as one that has null values for both of its children. For example, here a is MyTreeMap<String, Integer> built from such nodes, where the keys are peoples' names and the value associated with such a key is the person's age:

The picture above shows a tree with 4 nodes. Every path down from the root eventually get to an empty node. If you are doing a search and you arrive at an empty node, the key you are looking for is not in the tree. If you are doing a recursive insert and you get to an empty node, return a new leaf node with the key-value pair you are inserting.

The Node class has constructors, of course, but you will probably find it more convenient to use the Node factory method in the MyTreeMap class. The function

newLeafNode(key, value)

returns a new leaf node with its empty children attached. That should be the only type of Node you ever need to create. If you add to this method an increment of the tree's variable size, this variable will correctly maintain the number of nodes in the tree.

The Node class has a few other helpful methods. isLeaf( ) and isEmpty( ) tell you if a Node is empty or a leaf. setHeight() updates the height of a node to be 1 more than the maximum of the heights of its children. tallestChild( ) returns the taller of leftChild and rightChild. You can use these methods on leaf nodes but beware of calling them on an empty node; the null childen will cause them to crash.

Finally, the MyTreeMap class has a method treePrinter( ) that prints the keys in a tree level by level. You can use this to see if you are correctly building and adjusting the tree.

Part 2 - Binary Search Trees

Your first coding task is to implement the recursive search and insert methods. If you insert without calling the adjust( ) methods you will have a binay search tree. You need both insert and search before you can test anything. The search method is easier to code, so that might be a good place to start:

private V search(K key, Node t) This returns the value associated with the given key in the Binary Search Tree rooted at node t.

One possibility is that t is an empty node; if so just return null: there is no tree, so there is no node associated with the key.

If t is not empty you should call key.compareTo(t.key). If this is 0 return t.value. If it is negative return the result of searching on t's left child and if it is positive return the result of searching on t's right child.

Some people have a little more trouble wrapping their heads around the insert method:

private Node insert(K key, V value, Node t) This returns the tree derived from inserting the key-value pair in the tree rooted at t.

If t is an empty node you have recursed to the bottom of the tree; all that you need to do is to return a new leaf node (remember, there is a factory method for this) containing they key-value pair. If t is not empty you can compare key with t's key, just as you did in the search method. If the comparison is 0 you should change t's value to value and return t. If the comparison is negative or positive you replace t's left or right child with the result of a recursive call on t's left or right child, then reset t's height and return t.

At this point you should have a working map. The AVLTester program should run and give you some useful feedback. First, this program creates a MyTreeMap<String, Integer> with the following key-value pairs: {("Grape", 0) ("Plum, 1) ("Orange", 2) ("Pear", 3) ("Cherry", 4) ("Apple", 5) ("Strawberry", 6) ("Kiwi", 7) ("Lemon", 8) ("Lime", 9)}. After each addition it prints the height of the tree. This is 0 after ("Grape", 0), 1 after ("Plum", 1) and so forth. (You should be able to draw each of the reulting trees.) After the last addition the tree has height 5. Next, the program prints the size of the tree, which is 10 because there are 10 key-value pairs. It runs through all of the keys and does a search on each to print its value. Finally, for each level of the tree it runs through all of the nodes on that level and prints their keys. If you draw the tree on paper you should be able to match your drawing to the output of this program.

If the AVLTester program works as it should you are ready for the final part of this lab. If it doesn't work there is something wrong with either search( ) or insert( ); that needs to be fixed before you can proceed to Part 3.

 

Part 3 - Rebalancing AVL Trees

All that remains is the adjustI( ) method for rebalancing the tree after a call to insert( ). First, we will create calls to adjust( ). The insert( ) method has 4 cases:

  1. t is an empty node. In this case you return for t a new leaf node, which is already balanced; no call to adjust is needed
  2. key.compareTo(t.key) is 0. In this case you replace t's value with the new value. The shape of the tree isn't changed, so no call to adjust is needed.
  3. key.compareTo(t.key) is negative. In this case you have a recursive call t.leftChild = insert(key, value, t.leftChild) This could unbalance the tree, so before you return t you should check whether t satisfies the AVL property. If it does not you should replace t by a call to adjust(t) (i.e., t=adjust(t)). Reset the height on t, and return t.
  4. key.compareTo(t.key) is postive. This case is the mirror image of the case (c).

Now go to the method adjust(Node Z). Z is the node in the tree that has failed the AVL property. You need to assign to 9 variables: Y, X, a, b, c, t1, t2, t3, t4 and then build a new tree. The comments tell you what to do:

First, we want Y to be Z's tallest child, X to be Y's tallest child. The Node class has a tallestChild method. So we just say Y=Z.tallestChild( ) and X=Y.tallestChild( )

Next, we want a to be the one of X, Y, and Z with the smallest key, c to be the one with the largest key, and b to be the one in the middle. There are several ways to find these. Perhaps the simplest way is to look at the four cases:

  1. Y is Z's left child, X is Y's left child (i.e., Y==Z.leftChild && X==Y.leftChild)
    Because of the BST properties we know that X.key < Y .key < Z.key, so a=X, b=Y and c=Z
  2. Y is Z's left child, X is Y's right child:
    This time we know Y.key < Z.key and X.key is between Y.key and Z.key, so a=Y, B=X and c=Z
  3. You can look at the other two cases yourself

Next, we want t1 through t4 to be the four children of a, b, and c that are not themselves one of a, b, or c, ordered from smallest to largest. This is a mouthful to say, but it is easy to see with a picture. Consider case 1 from above:

We set t1=X.leftChild, t2=X.rightChild, t3=Y.rightChild, t4=Z.rightChild.

We do all 4 cases this way.

Once we have assigned all 9 variables we build the following tree:
We just have to assign 6 pointers: a.leftChild-t1, a.rightChild=t2, b.leftChild=a, etc.

Subtrees t1 through t4 are unchanged, but you should reset the heights of a and c before returning b. Remember that this is called from insert( ) with t = adjust(t). If insert then sets the height of node t you don't need to set the height of node b in adjust, but you should be sure it is set in one method or the other.

The AVLTester program should now print out the correct heights for an AVL tree. The AVL tree for the data I gave you only has height 3. Here is what you should see for the various levels:

Level 0: Orange
Level 1: Kiwi Plum
Level 2: Cherry Lemon Pear Strawberry
Level 3: Apple Grape Lime

handin

Make sure you've included your name at the top of MyTSreeMap.java Include in your submission a file named README. The contents of the README file should include the following:

  1. Your name and your partner's name if you worked with someone
  2. A statement of the Honor Pledge
  3. Any known problems with your classes or program

As usual, make a zipped copy of you project folder (which should be Lab6<your last name>) and hand it in on Blackbard as Lab 6.

 


Created April, 2020; modified Fall 2020 and spring 2021- Bob GeitzVI Powered