L-37 MCS 360 Wednesday 16 April 2003

The equivalent to binary search on an array is to use a binary tree. Traversing the tree in inorder then produces the sorted sequence. While search with such a binary tree also requires O(log(n)) operations on average (just like binary search), the tree has the additional advantage that inserting and deleting can be done much more efficiently than in case of an array.

We introduced binary trees in lecture 19. For completeness, the code is repeated here. We left off while still busy deleting elements from a tree, but will continue with this and balancing in the next lecture.

1. the file tree.h

The following gives the general prototype for a binary tree:
/* L-19 MCS 360 Wed 26 Feb 2003
 *    declaration of a binary tree,
 *    implemented with pointers to nodes  */

#define elements int

typedef struct node tree;   /* a tree starts at the root node */

struct node
{
   elements info;           /* data field of the node */
   tree *left;              /* pointer to the left child */
   tree *right;             /* pointer to the right child */
};

int empty ( tree *t );
/* postcondition: returns 1 if tree is empty, 0 otherwise; */

elements info ( tree *t );
/* precondition: empty(t) = 0;
 * postcondition: info(t) is the content of the node t; */

tree *create ( elements e );
/* postcondition: info(create(e)) == e; */

tree *left ( tree *t );
/* precondition: empty(t) == 0;
 * postcondition: left(t) is left child of t; */

tree *right ( tree *t );
/* precondition: empty(t) == 0;
 * postcondition: right(t) is right child of t; */

void *add_left ( tree *t, tree *child );
/* precondition: empty(t) == 0;
 * postcondition: left(t) == child; */

void *add_right ( tree *t, tree *child );
/* precondition: empty(t) == 0;
 * postcondition: right(t) == child; */

void destroy ( tree *t );
/* postcondition: empty(t) == 1; */

2. the file tree.c

Here is an implementation of a binary tree:
/* L-19 MCS 360 Wed 26 Feb 2003
 *   definition of the operations on a tree,
 *   implemented with pointers to nodes    */

#include <stdlib.h>
#include <assert.h>
#include "tree.h"

int empty ( tree *t )
{
   return (t == NULL ? 1 : 0);
}

elements info ( tree *t )
{
   assert(empty(t) == 0);

   return t->info;
}

tree *create ( elements e )
{
   tree *t;

   t = (tree*)calloc(1,sizeof(tree));
   t->info = e;
   t->left = NULL;
   t->right = NULL;

   return t;
}

tree *left ( tree *t )
{
   assert(empty(t) == 0);

   return t->left;
}

tree *right ( tree *t )
{
   assert(empty(t) == 0);

   return t->right;
}

void *add_left ( tree *t, tree *child )
{
   assert(empty(t) == 0);

   t->left = child;
}

void *add_right ( tree *t, tree *child )
{
   assert(empty(t) == 0);

   t->right = child;
}

void destroy ( tree *t )
{
   free(t);
}

3. the file table.h

The main data structure of this chapter is a table or dictionary. Such data structure is specific one, which uses more primitive data structures (like a binary tree) for its implementation. While the prototype below is specific for binary trees, a general Abstract Data Type for a table can easily be derived from this header file:
/* L-37 MCS 360 Wednesday 16 April 2003
 *   Prototypes for a table to store positive integers.  */

#include <stdio.h>
#include "tree.h"

tree *insert_element ( tree *t, int n );
/* inserts n into the tree, 
 * except if n already occurs,
 * then a message is printed */

tree *search_element ( tree *t, int n );
/* returns null if n does not occur in t,
 * otherwise returns t with info(t) == n */

tree *delete_element ( tree *t, int n );
/* delete_elements the node in the tree with info == n */

4. the file table.c

The file below contains the definitions of the main operations in the table. We have seen a recursive implementation for the search before, here we give an iterative version. The insert_element is copied from lecture 19 and was not discussed at all in class. We got left off today explaining the delete_element:
/* L-37 MCS 360 Wednesday 16 April 2003
 *   Definitions of the most essential operations on a table.  */

#include <stdio.h>
#include "table.h"

tree *insert_element ( tree *t, int n )
{
   if (empty(t) == 1)
      t = create(n);
   else if (info(t) > n)
      add_left(t,insert_element(left(t),n));
   else if (info(t) < n)
      add_right(t,insert_element(right(t),n));
   else
      printf("found duplicate number %d\n", n);

   return t;
}

tree *search_element ( tree *t, int n )
{
   tree *p = t;
   while ((p != NULL) && (info(p) != n))
      p = ( n < info(p) ? left(p) : right(p) );
   return p;
}

tree *delete_element ( tree *t, int n )
{
   tree *p = t;
   tree *father = NULL;
   while ((p != NULL) && (info(p) != n))            /* search for n */
   {
      father = p;
      p = ( n < info(p) ? left(p) : right(p) );
   }
   if (p == NULL)
      return t;
   else                                        /* p must be deleted */
   {
      tree *replacement;                      /* search replacement */
      if (left(p) == NULL)
         replacement = right(p);
      else if (right(p) == NULL)
         replacement = left(p);
      else                                        /* p has two sons */
      {
         tree *son,*f = p;
         replacement = right(p);          /* find inorder successor */
         son = left(replacement);
         while (son != NULL) 
         {
            f = replacement;
            replacement = son;
            son = left(replacement);
         }                      /* replacement == inorder successor */
         if (f != p)              /* father of replacement is not p */
         {                        
            add_left(f,right(replacement));
            add_right(replacement,right(p));
         }                      /* left(replacement) is always NULL */
         add_left(replacement,left(p));
      }

      if (father == NULL)                      /* p == root of tree */
         t = replacement;
      else if (p == left(father))                /* p is left child */
         add_left(father,replacement);
      else                                      /* p is right child */
         add_right(father,replacement);
      free(p);

      return t;
   }
}

5. the file balance.c

Below is the main program which tests all these operations and provides a very simple introduction to balancing trees. After the creation of the tree, the user can ask to search for elements. If the element is found, then it will be deleted, otherwise it will be inserted. Each time the tree is rebalanced with a left or right rotation.
/* L-37 MCS 360 Wednesday 16 April 2003
 *
 *   This program is an extension of that of lecture 19 where
 * a binary tree implemented with pointers was used to remove
 * duplicates from a list of positive integers. 
 *
 * The operations in this program illustrate balancing of trees. */

#include <stdio.h>
#include "table.h"

int height ( tree *t );
/* returns the height of the tree */

int balance ( tree *t );
/* returns the height(left(t)) - height(right(t)) */

tree *left_rotate ( tree *t );
/* performs one left rotation on the tree t */

tree *right_rotate ( tree *t );
/* performs one right rotation on the tree t */

tree *rebalance ( tree *t );
/* computes the balance of the root, and does one
 * rotation to the left or right to rebalance */

void write ( tree *t, int level );
/* writes the content of the tree, 
 * traversing the tree in preorder,
 * using level to make intendations */

int main ( void )
{
   tree *t = NULL;
   int n;

   do
   {
      printf("Give integers (<0 to stop) : ");
      scanf("%d", &n);
      if (n < 0) break;
      t = insert_element(t,n);
   } while (n >= 0);

   printf("The tree of numbers : \n");
   write(t,0);

   do
   {
      printf("Give integer to search for (<0 to stop) : ");
      scanf("%d", &n);
      if (n < 0) break;
      if (search_element(t,n) == NULL)
      {
         printf("%d does not occur in the tree\n", n);
         t = insert_element(t,n);
         printf("tree after insertion of %d :\n", n);
         write(t,0);
      }
      else
      {
         printf("%d does occur in the tree\n", n);
         t = delete_element(t,n);
         printf("tree after deletion of %d :\n", n);
         write(t,0);
      }
      t = rebalance(t);
      printf("tree after rebalancing : \n");
      write(t,0);
   } while (n >= 0);

   return 0;
}

int height ( tree *t )
{
   if (t == NULL)
      return -1;
   else
   {
      int hl = 1 + height(left(t));
      int hr = 1 + height(right(t));

      if (hl > hr)
         return hl;
      else
         return hr;
   }
}

int balance ( tree *t )
{
   if (t == NULL)
      return 0;
   else
      return height(left(t)) - height(right(t));
}

tree *left_rotate ( tree *t )
{
   if (t != NULL)
   {
      tree *p = right(t);

      if (p != NULL)
      {
         tree *tmp = left(p);

         add_left(p,t);
         add_right(t,tmp);
         t = p;
      }
   }
   return t;
}

tree *right_rotate ( tree *t )
{
   if (t != NULL)
   {
      tree *p = left(t);

      if (p != NULL)
      {
         tree *tmp = right(p);

         add_right(p,t);
         add_left(t,tmp);
         t = p;
      }
   }
   return t;
}

tree *rebalance ( tree *t )
{
   if (t != NULL)
   {
      int bal = balance(t);

      if (bal < 0)
         t = left_rotate(t);
      else if (bal > 0)
         t = right_rotate(t);
   }
}

void write ( tree *t, int level )
{
   if (empty(t) == 0)
   {
      int i;

      for (i=0; i<level; i++) printf("  ");

      printf("%d  height = %d  balance = %d\n",
             info(t), height(t), balance(t));

      write(left(t),level+1);
      write(right(t),level+1);
   }
}