tree.c
contents ::
  asgn3.c
  tree.c
  tree.h
  mylib.c
  mylib.h

/***************************************************************************
 *                                                                         *
 *     Tree Program in C                                                   *
 *                                                                         *
 *     COSC 242 Assignment 20/09/01                                        *
 *                                                                         *
 *     JAMES LITTLE                                                        *
 *                                                                         *
 ***************************************************************************/

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

/**************************
 *                        *
 *   IS_RED               *
 *   IS_BLACK             *
 *                        *
 **************************

 These are obviously not function calls, but they do evaluate in the same 
 manner.
 These are macros that are pre-processed into the C code and enforce robust 
 coding, through simplification of common statements. In a sense returning a
 boolean value depending on what the actual colour of the node is.
 
 PARAMETERS: x      = the tree node to be tested. 
  
 RETURN VALUE: some boolean value.

*/
#define IS_BLACK(x) ((NULL == x) || (BLACK == x->colour))
#define IS_RED(x)   ((NULL != x) && (RED == x->colour))

typedef enum colour_e { RED, BLACK } colour_t;

struct treenode {
  char *key;
  colour_t colour;
  tree left;
  tree right;
};

/**************************
 *                        *
 *   tree_new             *
 *                        *
 **************************

 A better name would be tree_null, since all this function call achieves is the
 return of a NULL value.
  
 RETURN VALUE: NULL.

*/
tree tree_new(){
  return NULL;
}

/**************************
 *                        *
 *   tree_search          *
 *                        *
 **************************

 Searching a binary tree is a simple exercise. Start at the root and move left
 or right, depending on whether the value you are seeking is less than or
 greater than that node.
 
 PARAMETERS: b      = the root of the tree we are searching. Or the current
                      node to look into.
              key    = the character string to look for.
  
 RETURN VALUE: a pointer of type char, or a character string, the one we were
 looking for hopefully.

*/
char *tree_search(tree b, char *key){
  if(b == NULL) return NULL;
  /* strcmp(cs,ct) returns 0  if cs==ct
     strcmp(cs,ct) returns <0 if cs<ct
     strcmp(cs,ct) returns >0 if cs>ct
  */
  if(!strcmp(key, b->key)) return b->key;
  else if(strcmp(key, b->key)<0) 
    return tree_search(b->left, key);
  else
    return tree_search(b->right, key);
}

/**************************
 *                        *
 *   tree_insert          *
 *                        *
 **************************

 This function creates the tree. And returns a valid tree, as opposed to the
 tree_new function that only returns NULL. Every node in a tree is a tree in
 its own right. This fact allows the function to recursively search for a NULL
 node and create a tree in its place, hence the same function is required for 
 an empty tree as is required for a non-empty tree.

 Not if, but when a NULL node is discovered, space from the heap is allocated
 to the tree-node. The key is copied into the key field of the node and both
 siblings of the node are initialised to NULL. Initialisation of the siblings
 to NULL is required so that sibling nodes may be attached at some later stage.
 Otherwise in true C fashion, both siblings would hold garbage and that
 wouldn't be fun.

 Initialising the colour of the newly created node to red is required by RBT
 insertion. Colour initialisation is not required by BST insertion, but it
 doesn't hinder program functionality either.

 The only required difference between RBT and BST insertion is that following
 RBT insertion, the tree must be checked for compatibility with RBT properties.
 This requires the use of the rbt_fix function. This function is repeatedly
 called as the recursion unwinds.
 
 PARAMETERS: b         = the root of the tree we are searching. Or the current
                         node to look into.
              key       = the character string to look for.
              rb_insert = indicates if RBT insertion should be used or standard
                          BST insertion.
  
 RETURN VALUE: the updated tree.

*/
tree tree_insert(tree b, char *key, int rb_insert){
  if(b == NULL){
    b = emalloc(sizeof *b);
    b->key = emalloc((strlen(key)+1) * sizeof (char));
    strcpy(b->key, key);
    b->colour = RED;
    b->left = NULL;
    b->right = NULL;
  } else if(strcmp(key, b->key)<0)
    b->left = tree_insert(b->left, key, rb_insert);
  else if(strcmp(key, b->key)>0)
    b->right = tree_insert(b->right, key, rb_insert);

  if(rb_insert)
    b = tree_fix(b);
  return b;
}

/**************************
 *                        *
 *   right_rotate         *
 *                        *
 **************************

 To explain right rotation:
 We pull the left child of r up, so r is now its left child's right child. 
 
 A graphical representation:
                (r)                        (r->left')
                /   \           ===>           /   \
        (r->left) (r->right)          (r->l->l')  (r')
          /    \                                  /  \
   (r->l->l)  (r->l->r)                   (r->l->r') (r->right')
 
 I use a ' to indicate that this is no longer referred to by this name, but
 that it was previously this node.

 PARAMETERS: r      = the node that will be moved and others will rotate.
  
 RETURN VALUE: a rotated tree.

*/
static tree right_rotate(tree r){
  if(r != NULL && r->left != NULL){
    tree temp = r;
    r = r->left;
    temp->left = r->right;
    r->right = temp;
  }
  return r;
}

/**************************
 *                        *
 *   left_rotate          *
 *                        *
 **************************

 The opposite of right rotation.

 PARAMETERS: r      =  the node that will be moved and others will rotate.
  
 RETURN VALUE: a rotated tree.

*/
static tree left_rotate(tree r){
  if(r != NULL && r->right != NULL){
    tree temp = r;
    r = r->right;
    temp->right = r->left;
    r->left = temp;
  }
  return r;
}

/**************************
 *                        *
 *   tree_fix             *
 *                        *
 **************************

 It is important that our red black trees conform to the red black tree
 properties:
 Black height is equal for all leaves, 
 no red node has red siblings, 
 and perhaps less importantly: a root node is black.
 
 Here we make sure that these properties hold. If they are broken, then we
 fix it up:
 If a child is red, and has a red child, then the action depends on the color
 of the lower red child's "uncle" [or aunty]. If the uncle is red, re-colour
 the initial node red, and its children black. 

 If a left child is red and has a red left child, and the right child is black.
 Then right rotate self and colour self black and children red.
 In the opposite case [red right child and has red right child, and the left
 child is black], left rotate self and colour self black and children red. 

 If the left child and its right child are red, and the right child is black.
 We left rotate the left child, this puts us into the above situation, so we
 use the same code and right rotate self and colour self black and children
 red.
 Again the opposite situation follows a similar algorithm: If the right child
 and its left child is red, and the left child is black.
 We right rotate the right child, left rotate self and colour self black and 
 children red.
 
 PARAMETERS: r      =  the node that should be examined.
  
 RETURN VALUE: a correct RBT tree.
*/
static tree tree_fix(tree r){
  if(r == NULL) return NULL;

  if(IS_RED(r->left) && 
     (IS_RED(r->left->left) || IS_RED(r->left->right))){

    if(IS_RED(r->right)){

      r->colour = RED;
      r->left->colour = BLACK;
      r->right->colour = BLACK;

    } else {

      if(IS_RED(r->left->right)) r->left = left_rotate(r->left);

      r = right_rotate(r);
      r->colour = BLACK;
      r->right->colour = RED;

    }

  } else if(IS_RED(r->right)  &&
             (IS_RED(r->right->left) || IS_RED(r->right->right))){

    if(IS_RED(r->left)){

      r->colour = RED;
      r->left->colour = BLACK;
      r->right->colour = BLACK;      

    } else {

      if(IS_RED(r->right->left)) r->right = right_rotate(r->right);

      r = left_rotate(r);
      r->colour = BLACK;
      r->left->colour = RED;

    }

  }
  return r;
}

/**************************
 *                        *
 *   black_height         *
 *                        *
 **************************

 Outputs the black height and height of each leaf.
 
 PARAMETERS: t      =  tree to calculate height of.
  
 RETURN VALUE: The average height of the tree.
*/
int black_height(tree t, int bh, int h){
  static int h_sum = 0;
  static int num = 0;


  if(t == NULL){
    printf("black height :: %d\ttotal height is :: %d\n", bh, h);
  } else {
    if(t->left == NULL && t->right == NULL){
      num++;
      h_sum += h;
    }
    h++;
    if(IS_BLACK(t))
      bh++;
    black_height(t->left, bh, h);
    black_height(t->right, bh, h);    
  }
  return h_sum/num;
}


/**************************
 *                        *
 *   tree_inorder         *
 *                        *
 **************************

 Uses the function passed to do something to the tree in order, lowest to 
 highest.
 
 PARAMETERS: b    = the current node to look into.
             f    = the function that implements some functionality.

 RETURN VALUE: NONE.
*/
void tree_inorder(tree b, void f(char *s)){
  if(b == NULL) return;
  tree_inorder(b->left, f);
  f(b->key);
  tree_inorder(b->right, f);
}

/**************************
 *                        *
 *   tree_preorder        *
 *                        *
 **************************

 Prints nodes down the left side of a tree first, then the right side.
 
 PARAMETERS: b      = the current node to look into.
              f      = the function that implements some functionality.
  
 RETURN VALUE: NONE.
*/
void tree_preorder(tree b, void f(char *s)){
  if(b == NULL) return;
  f(b->key);
  /* REMOVED FOR ASSIGNMENT
     printf("%c :: %s\n", (IS_BLACK(b))? 'B' : 'R', b->key);
  */
  tree_preorder(b->left, f);
  tree_preorder(b->right, f);
}

/**************************
 *                        *
 *   tree_postorder       *
 *                        *
 **************************

 Moves down the left side of a tree first, then the right side, then starts 
 printing nodes.
 
 PARAMETERS: b      = the current node to look into.
              f      = the function that implements some functionality.
  
 RETURN VALUE: NONE.
*/
void tree_postorder(tree b, void f(char *s)){
  if(b == NULL) return;
  tree_postorder(b->left, f);
  tree_postorder(b->right, f);
  f(b->key);
}


/**************************
 *                        *
 *   tree_delete          *
 *                        *
 **************************

 Only used for BST tree [have not implemented a tree delete algorithm for RBT]
 Rather simple algorithm in comparison to RBT insertion. We look for the key
 in a binary search style down the tree. If we come to a NULL node then it does
 not exist. 
 If we find it, then we delete it. If it has no children then we can just free
 its memory space. If it has one child then we free its memory space and make 
 the node point to its child. If it has two children, then we look for the
 lowest valued node in its right tree and replace it with that.
 
 PARAMETERS: b      = the root of the tree we are searching. Or the current
                      node to look into.
              key    = what we are looking for.
  
 RETURN VALUE: a tree that possibly has one less node.
*/
tree tree_delete(tree b, char *key){
  /* Key does not exist */
  if(b == NULL) return NULL;             
  if(strcmp(key, b->key)<0) 
    /* Key may be in the left sub tree */
    b->left = tree_delete(b->left, key); 
  else if(strcmp(key, b->key)>0)
    /* Key may be in the right sub tree */
    b->right = tree_delete(b->right, key);
  else { /* We have found it, now eradicate it! */
    tree temp = NULL;
    if(b->left!=NULL && b->right!=NULL){
      temp = b->right;
      while(temp->left!=NULL)
         temp = temp->left;
      free(b->key);
      b->key = emalloc((strlen(temp->key)+1) * sizeof (char));
      strcpy(b->key, temp->key);
      b->right = tree_delete(b->right, key);
    } else {
      if(b->left!=NULL)
         temp = b->left;
      else
         temp = b->right;
      free(b->key);
      free(b);
      b = temp;
    }
  }
  return b;
}

James Little