CS 2213/2211
 Advanced Programming
 Spring 2005

 Recitation 12:  Binary Trees
    Week 12: Apr 12-14
 Due (on time): 2005-04-19  23:59:59
 Due (late):        2005-04-24  23:59:59

Recitation 12 should be submitted following directions at: submissions with deadlines
  • 2005-04-19  23:59:59 (that's Tuesday, 19 April 2005, 11:59:59 pm) for full credit.
  • 2005-04-24  23:59:59 (that's Sunday, 24 April 2005, 11:59:59 pm) for 75% credit.


Introduction: This recitation works on areas:


Overview: Here is a fairly standard binary search tree with a char at each node. The recursive inorder traversal code is a natural choice because of its simplicity compared with the non-recursive code, which requires an additional stack. I am using a non-recursive insert algorithm because it is easier to understand than a recursive version. Notice that the algorithm ignores duplicates.

Binary search tree, inserting single chars
tree.c tree.h, treetest.h
/* tree.c: create binary tree */
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include "tree.h"

struct tnode {
   char ch;
   struct tnode *left;
   struct tnode *right;
};
static struct tnode *newnode(char );
static void treeprint2(struct tnode *);
struct tnode *root = NULL;

/* insert: add a node, non-recursive */
void insert(char c) {
   struct tnode *p;
   if (root == NULL) { /* empty tree */
      root = newnode(c);
      return;
   }
   p = root;
   for (;;) {
      if ((p -> ch) == c) return;/*dups */
      else if ((p -> ch) > c) { /* left */
         if ((p -> left) == NULL) {
            p -> left = newnode(c);
            return;
         }
         else p = p -> left;
      }
      else if ((p -> ch) < c) {/* right */
         if ((p -> right) == NULL) {
            p -> right = newnode(c);
            return;
         }
         else p = p -> right;
      }
   }
}
/* newnode: fix up a new node */
static struct tnode *newnode(char c) {
   struct tnode *p = (struct tnode *)
        malloc(sizeof(struct tnode));
   p -> ch = c;
   p -> left = p -> right = NULL;
   return p;
}

/* treeprint: recursive inorder print */
void treeprint(void) {
   treeprint2(root);
}

/* treeprint2: recursive inorder print */
static void treeprint2(struct tnode *p) {
   if (p != NULL) {
      treeprint2(p -> left);
      printf("%c", p -> ch);
      treeprint2(p -> right);
   }
}
-----------------------------------------
/* tree.h: header file for tree.c */
void insert(char );
void treeprint(void);
-----------------------------------------
/* treetest.c: test tree.c */
#include <stdio.h>
#include "tree.h"

int main() {
   char c;
   while ((c = getchar()) != '\n') {
      insert(c);
   }
   treeprint();
   printf("\n");
}
% cc -o tree tree.c treetest.c
% tree
thequickbrownfoxjumpsoverthelazydog
abcdefghijklmnopqrstuvwxyz


Randomize a file: If one processes a file that is already ordered with code to construct a binary search tree, the result is a single linked list with additional nodes set to NULL. For reasonable results, inputting words in random order works well. The code at the link below randomized the order of a file, so that a reasonable binary search tree will be built: Randomizing Program. This link shows a alphabetic file of 21 words randomized to another file of 21 words.

I used the above program to randomize the word file on the Sun Unix system. Here is the original word file: Original word list, and here is the randomized version: Randomized word list. (Each file has 25143 words in 206663 bytes.)


A. Convert the Program to Strings: For part A you are to convert the above binary search tree program to handle strings in C.

Requirements for Part A
  • Rewrite the program above so that it will make use of C strings instead of single characters.
  • We want to handle the same word list file as before, so in the end we need to treat uppercase letters as if they were lowercase, as was done for Recitation 11.
  • As in the previous program, you should read a file of words, insert into the binary search tree, and do an inorder traversal to print the words in sorted order.
  • You are supposed to be adapting the program above so that it will handle strings, not writing or finding a new C program to do this.
  • First work with the months file to test your program. (This is not "random" order, but certainly not alphabetic.)
  • Next work with the full word list file in random order: ranwords_unix. Here you must not print the result of an inorder traversal, but instead you should print it to a file, and print the first 20 and the last 20 entries of this file.


B. Add a lookup function: For this part you are to add a function with prototype: int lookup(char *);

Requirements for Part B
  • This function is similar to and simpler than the insert function.
  • The function should allow you to look up a word after the binary search tree has been built. lookup should return 0 in case the word is found, and it should return -1 if the word is not found.
  • Look up the words foxtail, atrophy, and hypertrophy, showing whether they are in the Unix wordlist of Part A or not.


C. Add extra pointers to make a bi-directional list: This is the hard part of this Recitation. Add two pointers to each tree node: prev, which always points to the previous node in alphabetic order, and next, which always points to the next node in alphabetic order.

Requirements for Part C
  • Add extra pointers prev and next, each of type struct tnode *, to the definition of the structure tnode. prev points to the immediately preceding node in alphabetic order. For the first node in alphabetic order, the prev field should be null. Similarly, next points to the immediately following node in alphabetic order. For the last node in alphabetic order, the next field should be null.
  • In addition to the rootpointer, there should also be a global pointer first pointing to the first node (whose prev field is null), and another global pointer last pointing to the last node (whose next field is null). (See the first diagram below for an illustration with three nodes.)
  • Add to the lookup function of Part B, so that in case it finds a word, it will print the five words preceding the word, the word itself, and the five words following the word being looked up. (See the second diagram below for a picture of adding a node, and see the illustrative output below.)
  • Test this first with the file months.
  • Finally test with the full word list file in random order: ranwords_unix


More Explanation: First consider a diagram of the tree of Part C with three nodes.

Diagram illustrating the tree of Part C with 3 nodes
 
  
 

Notice above that in addition to the binary search tree, this structure also includes pointers that connect all nodes into a bi-directional list in alphabetic order. Thus the code in the second column below will print all the words of the tree in alphabetic order, and the code in the third column will print the words in reverse alphabetic order. (This assumes that the data field has been renamed char *w;.)

Node: tnode Printing in order Printing in reverse
struct tnode {
   char *w;
   struct tnode *left;
   struct tnode *right;
   struct tnode *prev;
   struct tnode *next;
};
void printfromfirst(void) {
   struct tnode *p = first;
   while (p != NULL) {
      printf("%s\n", p -> w);
      p = p -> next;
   }
}
void printfromlast(void) {
   struct tnode *p = last;
   while (p != NULL) {
      printf("%s\n", p -> w);
      p = p -> prev;
   }
}

The most difficult part of this recitation is adjusting all the pointers when you add a node: a part of the rewritten insert function. The diagram below illustrates adding a fourth node to the tree of the previous diagram. After inserting Jan, Feb, and Mar, the diagram shows the insertion of Jun (not the order you would use from the months file). All the changes are in red. Here in addition to what was done before, one has to add new values for one prev and one next field, as well as changing the values in one prev and one next field. A new node added at the right is very similar. In the case of a node added at the far left (to the left of a node with prev field null), you only need to add two new values, and none need to be changed. The first pointer also has to have its value changed. The situation for a node added at the far right is similar.

After the first insertion (sort of a special case), you should have a single node, with all three pointers root, first, and last pointing to it, and with its prev and next fields null.

The pointer manipulations are fairly complicated. For a change this part of my code worked the first time I tried it, but I was very careful in drawing the diagrams and in writing out the statements.

Diagram showing the addition of a 4th node to the tree
 
  
 

Sample Output
Using file months Using file ranwords_unix
% cc -o treetest tree.c treetest.c
% treetest
Enter word to look up ---> Jul
Apr
Aug
Dec
Feb
Jan
Jul <----
Jun
Mar
May
Nov
Oct
% cc -o treetest tree.c treetest.c
% treetest
Enter word to look up ---> foxtail
fox
foxglove
Foxhall
foxhole
foxhound
foxtail <----
foxy
foyer
FPC
fraction
fractionate


What you should submit: Refer to the submissions directions and to deadlines at the top of this page. The text file that you submit should first have Your Name, the Course Number, and the Recitation Number. The rest of the file should have the following in it, in the order below, and clearly labeled, including at the beginning the appropriate item letters: a, b, c, etc.

 Contents of email submission for Recitation 12:

Last Name, First Name; Course Number; Recitation Number.

a. Code and results for Part A.

b. Code and results for Part B.

c. Code and results for Part C.


Revision date: 2005-04-08. (Please use ISO 8601, the International Standard.)
Copyright © 2011, Neal R. Wagner. Permission is granted to access, download, share, and distribute, as long as this notice remains.