Data Structures, Algorithms, & Applications in Java
Chapter 16, Exercise 15

We shall develop AVLtree as a derived class of the binary search tree class BinarySearchTree. The shortest and simplest codes result from the use of recursion. However, since our primary motivation to use AVL trees is run time efficiency, we shall develop the codes as iterative ones. The codes we develop follow the development in the text very closely. A faster implementation can be obtained by using a head node as the parent of the root and a tail node in place of the null pointer. The use of a parent pointer field in each node will also speed the code.

First we develop a class AVLElement for the elements in the binary search tree tree. This class is a top-level nested class of AVLtree. The code is given below.
// top-level nested class
static class AVLElement
{
   // data members
   Object element;        // element in node
   int bf;                // balance factor of node

   // constructors
   AVLElement(int theBalanceFactor, Object theElement)
   {
      bf = theBalanceFactor;
      element = theElement;
   }

   AVLElement(Object theElement)
      {element = theElement;}

   /** @return equivalent string suitable for output */
   public String toString()
      {return element.toString();}
}



The code to search for an element invokes the search method for the super class and then extracts the element from the object returned by the super class's search method.
/** @return element whose key is theKey
  * @return null if there is no element with key theKey */
public Object get(Object theKey)
{
   Object theElement = super.get(theKey);
   if (theElement == null)
      // no matching element
      return null;
   else
      return ((AVLElement) theElement).element;
}



To assist in the development of the code for the insert operation, we first develop methods to adjust balance factors and to perform the different types of rotations needed to restore balance. The method fixBF is used to change the balance factors of nodes on the path form a node q to (but not including) the newly inserted node r. The balance factors of all these nodes were 0 prior to the insertion. theKey is the key of the newly inserted element. It is used to construct the path to the newly inserted node.
/** set balance factors on path from q to r */
static void fixBF(BinaryTreeNode q, BinaryTreeNode r, Comparable theKey)
{// Balance factors from q to r were originally 0.
 // They need to be changed to +1 or -1.
 // Use theKey to find path from q to r.

   while (q != r)
      if (theKey.compareTo(((Data) q.element).key) < 0)
      {// height of left subtree has increased
         ((AVLElement) ((Data) q.element).element).bf = 1;
         q = q.leftChild;
      }
      else
      {// height of right subtree has increased
         ((AVLElement) ((Data) q.element).element).bf = -1;
         q = q.rightChild;
      }
}



The methods to perform the four different types of rotations are given below. They do the pointer and balance factor changes required by Figures 16.4 (LL rotation), 16.5 (LR rotation), and the corresponding figures for RR and RL rotations (see solution to Exercise 12).
/** LL rotation around a, pa is parent of a and b is left child of a */
void llRotate(BinaryTreeNode pa, BinaryTreeNode a, BinaryTreeNode b)
{
   // restructure subtree at A
   a.leftChild = b.rightChild;
   b.rightChild = a;
   if (pa != null)
      // a is not the root
      if (a == pa.leftChild)
         pa.leftChild = b;
      else
         pa.rightChild = b;
   else
      root = b;

   // set balance factors
   ((AVLElement) ((Data) a.element).element).bf = 0;
   ((AVLElement) ((Data) b.element).element).bf = 0;
}

/** RR rotation around a, pa is parent of a and b is right child of a */
void rrRotate(BinaryTreeNode pa, BinaryTreeNode a, BinaryTreeNode b)
{
   // restructure subtree at a
   a.rightChild = b.leftChild;
   b.leftChild = a;
   if (pa != null)
      // a is not the root
      if (a == pa.leftChild)
         pa.leftChild = b;
      else
         pa.rightChild = b;
   else
      root = b;

   // set balance factors
   ((AVLElement) ((Data) a.element).element).bf = 0;
   ((AVLElement) ((Data) b.element).element).bf = 0;
}

/** LR rotation around a, pa is parent of a and b is left child of a */
void lrRotate(BinaryTreeNode pa, BinaryTreeNode a, BinaryTreeNode b)
{
   BinaryTreeNode c = b.rightChild;

   // restructure subtree at a
   a.leftChild = c.rightChild;
   b.rightChild = c.leftChild;
   c.leftChild = b;
   c.rightChild = a;
   if (pa != null)
      // a is not the root
      if (a == pa.leftChild)
         pa.leftChild = c;
      else 
         pa.rightChild = c;
   else 
      root = c;

   // set balance factors
   int bfOfC = ((AVLElement) ((Data) c.element).element).bf;
   if (bfOfC == 1)
   {   
      ((AVLElement) ((Data) a.element).element).bf = -1;
      ((AVLElement) ((Data) b.element).element).bf = 0;
   }
   else
      if (bfOfC == -1)
      {
         ((AVLElement) ((Data) a.element).element).bf = 0;
         ((AVLElement) ((Data) b.element).element).bf = 1;
      }
      else
      {// bfOfC = 0
         ((AVLElement) ((Data) a.element).element).bf = 0;
         ((AVLElement) ((Data) b.element).element).bf = 0;
      }
   ((AVLElement) ((Data) c.element).element).bf = 0;
}

/** RL rotation around a, pa is parent of a and b is right child of a */
void rlRotate(BinaryTreeNode pa, BinaryTreeNode a, BinaryTreeNode b)
{
   BinaryTreeNode c = b.leftChild;

   // restructure subtree at a
   a.rightChild = c.leftChild;
   b.leftChild = c.rightChild;
   c.leftChild = a;
   c.rightChild = b;
   if (pa != null)
      // a is not the root
      if (a == pa.leftChild)
         pa.leftChild = c;
      else 
         pa.rightChild = c;
   else 
      root = c;

   // set balance factors
   int bfOfC = ((AVLElement) ((Data) c.element).element).bf;
   if (bfOfC == 1)
   {   
      ((AVLElement) ((Data) a.element).element).bf = 0;
      ((AVLElement) ((Data) b.element).element).bf = -1;
   }
   else
      if (bfOfC == -1)
      {
         ((AVLElement) ((Data) a.element).element).bf = 1;
         ((AVLElement) ((Data) b.element).element).bf = 0;
      }
      else
      {// bfOfC = 0
         ((AVLElement) ((Data) a.element).element).bf = 0;
         ((AVLElement) ((Data) b.element).element).bf = 0;
      }
   ((AVLElement) ((Data) c.element).element).bf = 0;
}



The code for the insert operation is given below. The first part of the code searches for the place to insert the new element using a process identical to that used to insert an element into an unbalanced search tree. In this part, we also keep track of the most recently seen node whose current balance factor is +1 or -1. Following the insertion, this node is the candidate node for the A = a node around which a rotation (if necessary) is to be performed. The nature of the imbalance (if any) at A is determined, and the appropriate rotation performed.
/** insert an element with the specified key
  * overwrite old element if there is already an
  * element with the given key 
  * @return old element (if any) with key = theKey */
public Object put(Object theKey, Object theElement)
{
   BinaryTreeNode p = root,     // search pointer
                  pp = null,    // parent of p
                  a = null,     // node with bf != 0
                  pa = null;    // parent of a

   // find place to insert theElement
   // also record most recent node with bf != 0
   // in a and its parent in pa
   Comparable elementKey = (Comparable) theKey;
   while (p != null)
   {// examine p.element.key
      Data pData = (Data) p.element;
      if (((AVLElement) pData.element).bf != 0)
      {// new candidate for a node
         a = p;
         pa = pp;
      }
      pp = p;
      // move p to a child
      if (elementKey.compareTo(pData.key) < 0)
         p = p.leftChild;
      else if (elementKey.compareTo(pData.key) > 0)
              p = p.rightChild;
           else
           {// overwrite element with same key
              Object elementToReturn = ((AVLElement) pData.element).element;
              ((AVLElement) pData.element).element = theElement;
              return elementToReturn;
           }
   }

   // get a node for theElement and attach to pp
   AVLElement q = new AVLElement(0, theElement);
   BinaryTreeNode r = new BinaryTreeNode
                          (new Data(elementKey, q));
   if (root != null)
      // the tree is not empty
      if (elementKey.compareTo(((Data) pp.element).key) < 0)
         pp.leftChild = r;
      else
         pp.rightChild = r;
   else
   {// insertion into empty tree
      root = r;
      return null;
   }

   // see if we must rebalance or simply change balance factors
   if (a != null)
   {// possible rebalancing needed
      Data aData = (Data) a.element;
      if (((AVLElement) aData.element).bf < 0)
         // bf = -1 before insertion
         if (elementKey.compareTo(aData.key) < 0)
         {// insertion in left subtree
            // height of left subtree has increased by 1
            // new bf of a is 0, no rebalancing
            ((AVLElement) aData.element).bf = 0;
            // fix bf on path from a to r
            fixBF(a.leftChild, r, elementKey);
         }
         else
         {// insertion in right subtree
            // bf of a is -2, rebalance
            BinaryTreeNode b = a.rightChild;
            if (elementKey.compareTo(((Data) b.element).key) > 0)
            {// RR case
               fixBF(b.rightChild, r, elementKey);
               rrRotate(pa, a, b);
            }
            else
            {// RL case
               fixBF(b.leftChild, r, elementKey);
               rlRotate(pa, a, b);
            }
         }
      else // bf = +1 before insertion
         if (elementKey.compareTo(aData.key) > 0)
         {// insertion in right subtree
            // height of right subtree has increased by 1
            // new bf of a is 0, no rebalancing
            ((AVLElement) aData.element).bf = 0;
            // fix bf on path from a to r
            fixBF(a.rightChild, r, elementKey);
         }
         else
         {// insertion in left subtree
            // bf of a is +2, rebalance
            BinaryTreeNode b = a.leftChild;
            if (elementKey.compareTo(((Data) b.element).key) < 0)
            {// LL case
               fixBF(b.leftChild, r, elementKey);
               llRotate(pa, a, b);
            }
            else
            {// LR case
               fixBF(b.rightChild, r, elementKey);
               lrRotate(pa, a, b);
            }
         }
   }
   else // a is null, no rebalancing
      fixBF(root, r, elementKey);

   return null;
}



The code to delete an element has two parts. The first part is very similar to the code used to delete an element from an unbalanced binary search tree. That code is augmented by code to save the path from the root to the parent of the physically deleted node. This path is saved on a stack because we will be traversing this path backwards during the resturcturing phase.

The rebalancing code follows the discussion in the text very closely. It also makes use of the fact that the following pairs of rotations are indentical LL and R1, LR and R-1, RR and L-1, and RL and L1. It also uses the fact that LL and R0 and RR and L0 rotations differ only in the final balance factors. See the solution to Exercise 14 for the figures for L0, L1, and L-1 rotations. The L0 and R0 cases would run a bit faster if we remove the code to set the balance factors of a and b from llRotate and rrRotate and insert code at the appropriate places in put and remove to do this.
/** @return matching element and remove it
  * @return null if no matching element */
public Object remove(Object theKey)
{
   Comparable searchKey = (Comparable) theKey;

   // define a stack to hold path taken from root
   // to physically deleted node
   ArrayStack stack = new ArrayStack();

   // p will eventually to point to node with key theKey
   BinaryTreeNode p = root; // search pointer
   while (p != null && !theKey.equals(((Data) p.element).key))
   {// move to a child of p
      stack.push(p);
      if (searchKey.compareTo(((Data) p.element).key) < 0)
         p = p.leftChild;
      else
         p = p.rightChild;
   }

   if (p == null)
      // no element with key theKey
      return null;

   // save element that will be removed
   Object theElement = ((AVLElement) ((Data) p.element).element).element;

   // restructure tree
   // handle case when p has two children
   if (p.leftChild != null && p.rightChild != null)
   {// p has two children, convert to 0 or 1 child case
      // find largest element in left subtree of p
      stack.push(p);
      BinaryTreeNode s = p.leftChild;
      while (s.rightChild != null)
      {// move to larger element
         stack.push(s);
         s = s.rightChild;
      }

      // move largest from s to p
      ((Data) p.element).key = ((Data) s.element).key;
      ((AVLElement) ((Data) p.element).element).element =
             ((AVLElement) ((Data) s.element).element).element;
      p = s;
   }

   // p has at most one child
   // save child pointer in c
   BinaryTreeNode c;
   if (p.leftChild != null)
      c = p.leftChild;
   else
      c = p.rightChild;

   // delete p
   if (p == root)
      root = c;
   else
      // is p a left or right child?
      if (p == ((BinaryTreeNode) stack.peek()).leftChild)
         ((BinaryTreeNode) stack.peek()).leftChild = c;
      else
         ((BinaryTreeNode) stack.peek()).rightChild = c;

   Comparable pKey = ((Data) p.element).key;
             // pKey may not equal theKey

   // rebalance tree and correct balance factors
   // use stack to retrace path to root
   // set q to parent of deleted node
   BinaryTreeNode q;
   try {q = (BinaryTreeNode) stack.pop();}
   catch (Exception e)
      {return theElement;}  // root was deleted

   while (q != null)
   {
      Data qData = (Data) q.element;
      AVLElement qAVL = (AVLElement) qData.element;
      if (pKey.compareTo(qData.key) <= 0)
      {// node p was deleted from the left subtree of q
         // height of left subtree reduced by 1
         qAVL.bf--;
         if (qAVL.bf == -1)
            // height of q is unchanged, nothing more to do
            return theElement;
         if (qAVL.bf == -2)
         {// q is unbalanced, classify unbalance and rotate
            BinaryTreeNode b = q.rightChild,
                           pa;  // q is a node
                                // pa is parent of a
            try {pa = (BinaryTreeNode) stack.pop();}
            catch (Exception e)
               {pa = null;}     // stack empty
            AVLElement bAVL = (AVLElement) ((Data) b.element).element;
            switch (bAVL.bf)
            {
               case 0:           // L0 imbalance
                  rrRotate(pa, q, b);
                  bAVL.bf = 1;
                  qAVL.bf = -1;  // q is a node
                  return theElement;
               case 1:           // L1 imbalance
                  rlRotate(pa, q, b);
                  break;         // must continue on path to root
               case -1:          // L-1 imbalance
                  rrRotate(pa, q, b);
            }
            q = pa;
         }
         else
            // qAVL.bf is 0
            try {q = (BinaryTreeNode) stack.pop();}
            catch (Exception e)
               {q = null;}     // stack empty
      }
      else
      {// pKey > qData.key
         // deleted from right subtree of q
         // height of right subtree reduced by 1
         qAVL.bf++;
         if (qAVL.bf == 1)
            // height of q is unchanged, nothing more to do
            return theElement;
         if (qAVL.bf == 2)
         {// q is unbalanced
            // classify imbalance and rotate
            BinaryTreeNode b = q.leftChild,
                           pa;  // q is a node
                                // pa is parent of a
            try {pa = (BinaryTreeNode) stack.pop();}
            catch (Exception e)
               {pa = null;}     // stack empty
            AVLElement bAVL = (AVLElement) ((Data) b.element).element;
            switch (bAVL.bf)
            {
               case 0:                // R0 imbalance
                  llRotate(pa, q, b);
                  bAVL.bf = -1;
                  qAVL.bf = 1;        // q is a node
                  return theElement;
               case 1:                // R1 imbalance
                  llRotate(pa, q, b);
                  break;              // must continue on path to root
               case -1:               // R-1 imbalance
                  lrRotate(pa, q, b);
            }
            q = pa;
         }
         else
            // qAVL.bf is 0
            try {q = (BinaryTreeNode) stack.pop();}
            catch (Exception e)
               {q = null;}     // stack empty
      }   
   }     

   return theElement;
}



The elements can be output in ascending order by performing an inorder traversal. The method for this is inherited from the super class BinarySearchTree.

The complexity of get, put, and remove is O(h), where h is the height of the input tree. Since the tree is an AVL tree, its height is O(log n). The ascend operation performs an inorder traversal which takes O(n) time.

All codes, test data, and output can be found in the files AVLtree.*.