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

First we must decide how we are going to incorporate the field leftSize. We can either require the user to include this field as one of the fields of the elements he/she will insert into the search tree, or we can define a new type IndexedElement with a leftSize field and a field element of type Object. In our implementation of IndexedBinarySearchTree we have gone the second route.

The code for the class IndexedElement is given below.
static class IndexedElement
{
   // data members
   Object element;        // element in node
   int leftSize;          // number of nodes in left subtree

   // constructors
   IndexedElement(int theLeftSize, Object theElement)
   {
      leftSize = theLeftSize;
      element = theElement;
   }

   IndexedElement(Object theElement)
      {element = theElement;}

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



The class IndexedBinarySearchTree is derived from BinarySearchTree. The method ascend as well as the constructor are simply inherited from the base class.

The code for the method get(theKey) invokes BinarySearchTree.get(theKey), which returns an element of type IndexedElement. The element field of this returned object is extracted and returned as the result. The code is given below.
/** @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 ((IndexedElement) theElement).element;
}



To facilitate the implementation of the method get(theIndex), we first develop the method getDataElement(theIndex), which returns the element field (this is of type Data) of the node whose index is theIndex.
/** @return Data object of element whose index is theIndex
  * @return null if there is no element with index theIndex */
Data getDataElement(int theIndex)
{
   BinaryTreeNode p = root;
   while (p != null)
   {
      IndexedElement q = (IndexedElement) ((Data) p.element).element;
      if (theIndex < q.leftSize)
         // desired element is in left subtree
         p = p.leftChild;
      else
         if (theIndex > q.leftSize)
         {// desired element is in left subtree
   	        theIndex -= q.leftSize + 1;
             p = p.rightChild;
         }
         else 
            // found desired element
            return (Data) p.element; 
   }

   // no element with index equal to theIndex
   return null;
}



Now, we use the method getDataElement to arrive at the code for get(theIndex).
/** @return element whose index is theIndex
  * @return null if there is no element with index theIndex */
public Object get(int theIndex)
{
   Data theData = getDataElement(theIndex);
   if (theData == null)
      // no element with index theIndex
      return null;
   else
      return ((IndexedElement) theData.element).element;
}



An insert may be performed in two phases. First, an insert is done assuming we have an ordinary binary search tree (not an indexed one). If the tree did not already contain an element with the given key, then another pass is made from the root to the newly inserted node adjusting the value of leftSize as needed. If we use BinarySearchTree.put for the first pass, we must determine the size of the search tree before and after the invocation of BinarySearchTree.put so that we can determine whether or not the second pass is to be made. An alternative to this is to just replicate the code of BinarySearchTree.put into IndexedBinarySearchTree.put. We take this alternative route below.
/** 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
   Comparable elementKey = (Comparable) theKey;
   // find place to insert theElement
   while (p != null)
   {// examine p.element.key
      pp = p;
      // move p to a child
      if (elementKey.compareTo(((Data) p.element).key) < 0)
         p = p.leftChild;
      else if (elementKey.compareTo(((Data) p.element).key) > 0)
              p = p.rightChild;
           else
           {// overwrite element with same key
              Object elementToReturn = ((Data) p.element).element;
              ((Data) p.element).element = theElement;
              return elementToReturn;
           }
   }

   // get a node for theElement and attach to pp
   IndexedElement q = new IndexedElement(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;

   // a new element has been inserted , update leftSize
   // by following search path to elementKey
   p = root;
   while (true)
      if (elementKey.compareTo(((Data) p.element).key) < 0)
      {// theElement was inserted in left subtree of p
         ((IndexedElement) ((Data) p.element).element).leftSize++;
         p = p.leftChild;
      }
      else
         if (elementKey.compareTo(((Data) p.element).key) > 0)
            // theElement was inserted in right subtree of p
            p = p.rightChild;
         else
            // reached newly inserted element
            return null;
}





An element may be removed using a two step algorithm. In the first, a search is made to verify the presence of the desired element. In the second, leftSize fields are adjusted and the element removed. The code is given below.
/** @return matching element and remove it
  * @return null if no matching element */
public Object remove(Object theKey)
{
   Object theElement = get(theKey);
   if (theElement == null)
      // no matching element
      return null;

   // there is a matching element, remove it
   theRemove(theKey);

   return theElement;
}



The method theRemove is similar to BinarySearchTree.remove except that it knows that the delete will succeed and it also updates leftSize fields. The code is given below.
/** remove the element with key equal to theKey
  * such an element is guaranteed to exist in the tree */
void theRemove(Object theKey)
{
   Comparable searchKey = (Comparable) theKey;

   // p will eventually point to node with key searchKey
   BinaryTreeNode p = root,    // search pointer
                  pp = null;   // parent of p

   // follow path to searchKey decrementing leftSize each time
   // we move to a left subtree
   while (!((Data) p.element).key.equals(searchKey))
   {// move to a child of p
      pp = p;
      if (searchKey.compareTo(((Data) p.element).key) < 0)
      {
         ((IndexedElement) ((Data) p.element).element).leftSize--;
         p = p.leftChild;
      }
      else
         p = p.rightChild;
   }

   // restructure tree
   // handle case when p has two children
   if (p.leftChild != null && p.rightChild != null)
   {// two children
      // convert to zero or one child case
      // find element with largest key in left subtree of p
      int ls = ((IndexedElement) ((Data) p.element).element)
                                   .leftSize - 1; // save value
      BinaryTreeNode s = p.leftChild,
                     ps = p;  // parent of s
      while (s.rightChild != null)
      {// move to larger element
         ps = s;
         s = s.rightChild;
      }

      // move largest element from s to p
      p.element = s.element;
      ((IndexedElement) ((Data) p.element).element).leftSize = ls;
      p = s;
      pp = ps;
   }

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

   // remove node p
   if (p == root) root = c;
   else
   {// is p left or right child of pp?
      if (p == pp.leftChild)
         pp.leftChild = c;
      else
         pp.rightChild = c;
   }
}



An indexed delete may be done in a similar way. The code is given below.
/** @return element with index theIndex and remove it
  * @return null if there is no element with this index */
  public Object remove(int theIndex)
{
   Data theData = getDataElement(theIndex);
   if (theData == null)
      // no element with index theIndex
      return null;

   // there is an element with index theIndex, remove it
   theRemove(theData.key);

   return ((IndexedElement) theData.element).element;
}



Our insert and delete codes penalize successful insertions and deletions over unsuccessful ones. Since we would normally expect inserts and deletes to succeed, we may rewrite the code so as to update leftSize fields on the first pass assuming that the update is necessary. In case it turns out that the update was not to be done, a second pass is made and leftSize values reset.

The get, insert, and delete methods have complexity O(h), where h is the height of the search tree. The relevant files are IndexedBinarySearchTree.*.