Copyright, 1997, Susan Anderson-Freed

Basic Structures

In your second programming course, you learned about several linear data structures (stacks, queues, linked lists). Although these structures differed in where insertions and deletions could take place, the structures used a similar two field organization. Thus, one field consisted of the data component, while the second served to link the structure's nodes. The following table illustratrates this approach for our stack of shapes.

Shape Field:

Name
Sides
Radius
Link Field:

A pointer to a stack
(or queue
or linked list).


Notice that the link field could point to a queue or a list just as easily.

We are about to examine two new structures which are not linear: trees and graphs. The following table briefly compares these structures.

Data Structure Stack Queue List Binary Search Tree Graph
Insertions Top Rear Anywhere Leaf Node Edge
Deletions Top Front Anywhere Anywhere Edge
Basic StructureLinear Linear Linear Hierarchical Network


Dynamically linked trees

A tree is a non-linear structure. As such it has more than one link field for each node in the data structure. The simplest trees contain only two link nodes, and thus are called Binary Trees.

Typically, we distinguish trees based on their representation, reserving the term, tree, for structures that are encoded as dynamically linked lists. We also could use an array encoding for a tree. Such structures are generally called heaps to distinguish them from their dynamically linked counterparts.

General Characteristics of Binary Search Trees

A binary search tree has the following characteristics

  1. It has a node which is referred to as the root node. This node represents the starting node of the tree.
  2. Each node, including the root node, has at most two link nodes referred to as children.
  3. Each node contains a key field which determines the node's place in the tree. Keys may be redundant or unique. We'll assume that they are unique.
  4. The left child always contains a key value that is less than the key value of its parent.
  5. The right child always contains a key value that is greater than the key of its parent.
  6. Leaf Nodes are nodes whose left and right pointers are NULL.
Basic Representation

The general representation of a dynamically linked tree appears in the following table:

Link to Left Child Data Field(s) Link To Right Child


As this table indicates, a tree node contains at least one data field and two link nodes.

Traversals

Unlike linear structures, a tree structure can be traversed in several different ways. In fact there are four common traversals for a BST (Binary Search Tree). These traversals are:

  1. Preorder Traversal. The preorder traversal uses the following pattern. Assume that the traversal prints the contents of each node.
    • Print the node's data fields
    • Visit the left child
    • Visit the right child

    This traversal is abbreviated as NLR (Node, Left, Right). It incorporates a stack algorithm and is usually coded as a recursive function. Its name is derived from prefix notation. (A preorder traversal of an expression tree produces the expression written in prefix notation.)

  2. Inorder Traversal. This traversal uses the following pattern.
    • Visit the left child
    • Print the node's data fields
    • Visit the right child

    This traversal is abbreviated as LNR(Left, Node, Right). It also is a stack algorithm and is coded as a recursive function. Its name is derived from infix notation.

  3. Postorder Traversal. This traversal uses the following pattern.
    • Visit the left child
    • Visit the right child
    • Print the node's data fields

    This traversal is abbreviated as LRN(Left, Right, Node). It also is a stack algorithm and is coded as a recursive function. Its name is derived from postfix notation.

  4. Level Order Traversal. This traversal uses the following pattern.
    • Visit the root node
    • Visit the root node's children, beginning with the left most child
    • Visit the root node's grandchildren, beginning with the leftmost grandchild.
    • Continue until all of the nodes have been visited

    This traversal has NO abbreviation. It is a queue algorithm, and is encoded as an iterative function. Its name is derived from its traversal pattern since it visits all the nodes on a level beginning with the leftmost node. When it has finished with one level, it moves to the next lower level.

The Tree menu and main() function

To illustrate tree insertions and traversals, we're going to create a simple menu system as illustrated by the following table.

The Tree menu
                        The TREE MENU
1. Add a node to the tree
2. Preorder Traversal
3. Inorder traversal
4. Postorder Traversal
5. Level Order Traversal
6. QUIT


The function that produces this menu is a simple variation of the print_menu() function that we used for the shapes stack.

The tree type definitions

The creation of the tree uses code that is similar to the creation of the dynamically linked stack; however, the tree structure must contains two pointers rather than one. The following table contains the struct definition as well as the function prototypes. Notice that we must explicitly pass in the pointers to the tree. Unlike C++, C does not provide information hiding capabilities.

#include <stdio.h>
#include <stdlib.h>

#define IS_FULL(ptr) (!(ptr))

typedef struct node *tree_ptr;
typedef	struct node	{
          tree_ptr left_child;
	  int key;
	  tree_ptr  right_child;
	};
	
char print_menu();

tree_ptr new_node();
void add_node(tree_ptr *, int);
void preorder(tree_ptr, int *);
void inorder(tree_ptr, int *);
void postorder(tree_ptr, int *);
void level_order(tree_ptr);	


The main() function

The main() function appears in the following table. Examine the function calls used in the switch statement. Compare the calls with the prototypes that appear in the definitions section.

The main() function
int main()
{
   /* create a dynamically linked tree */

   int choice = print_menu();
   int num;
    int level;
    tree_ptr root;
   
   root = NULL;
  
   while (choice != 6) {
      switch(choice) {
	case 1:  printf("Enter the number to insert: ");
		 scanf("%d",&num);
		 add_node(&root, num);
		 printf("\n\nThe tree contains\n\n");
		 level = 1;
		 preorder(root, &level);
		 break;
	case 2:  printf("\n\nPreorder traversal:\n\n");
		 level = 1;
		 preorder(root, &level);
		 break;
	case 3:  printf("\n\ninorder traversal:\n\n");
	 	 level = 0;
		 inorder(root, &level);
		 break;
	case 4:  printf("postorder traversal:\n\n");
	         level = 0;
		 postorder(root, &level);
		 break;
	case 5: printf("level_order traversal:\n\n");
		level_order(root);	
      }
    choice = print_menu();
   }
}


Inserting a node into a tree

We use two functions to add a node to the tree. The first function, new_node allocates the storage for the node. The code is:

The new_node() function
tree_ptr new_node()
{
 /* get a new node, check to see if memory is full */
 tree_ptr ptr = (tree_ptr) malloc(sizeof(struct node));
 if (!ptr) {
   fprintf(stderr, "The memory is full\n");
   exit(1);
 }
 return ptr;
}


The second function, add_node, recursively searches the tree until it finds the correct place to insert the node. The correct place is determined as follows:

  1. If the new node is inserted into the first leaf node it encounters.
  2. If the key value in the new node is less than the key value in the current node, the left child is followed.
  3. If the key value in the new node is greater than the key value in the current node, the right child is followed.
  4. If the key value in the new node is equal to the key value in the current node, a message is printed which indicates that the key already exists. (Note: An alternate approach would include a count field in each tree node. IF a duplicate key is encountered, the count field is incremented.)
The code for the add_node function is found in the following table.

The add_node() function
void add_node(tree_ptr *node, int num)
/* add a node to the tree */
/* pass in the address of the ptr to the current node */
{
  tree_ptr ptr = *node;
  if (!ptr)  {
     ptr = new_node();
     ptr->key = num;
     ptr->left_child =  ptr->right_child = NULL;
     *node = ptr;
  }
  else if (num == ptr->key)
    fprintf(stderr, "The number is currently in the tree\n");
  else if (num < ptr->key)
     add_node(&ptr->left_child, num);
  else
     add_node(&ptr->right_child, num);
}


Preorder traversal

The preorder traversal processes the node first. In this case, it prints out the key value. The left branch is followed recursively, then the right branch. The code is simple and is found in the following table.

void preorder(tree_ptr ptr, int *level)
/* preorder tree traversal */
{
  int i;
  if (ptr) {
    printf("Level %d:", *level);
    for (i = 1; i <= *level; i++)
       printf("     ");
    printf("%d\n",ptr->key);
    (*level)++;
    preorder(ptr->left_child, level);
    preorder(ptr->right_child, level);
    (*level)--;
  }
}


The preorder function includes a level parameter that indicates the node's level in the tree. Level 1 is the root node. Level 2 contains the root's children, and so on. The key fields are indented to show the tree structure.

The following table contains a sample run.

A sample run (Input sequence: 60,50,40,70, 80,55,75,65)
Preorder traversal:

Level 1:     60
Level 2:          50
Level 3:               40
Level 3:               55
Level 2:          70
Level 3:               65
Level 3:               80
Level 4:                    75



Inorder traversal

The inorder traversal follows the left branch of the tree until it reaches a null node. It then backtracks to the left leaf node and prints the key value. The right branch is then followed.

The inorder traversal produces a sorted list of the nodes based on the key value. The code is found in the following table.

void inorder(tree_ptr ptr, int *level)
/* inorder tree traversal */
{
   int i;
   if (ptr) {
      (*level)++;
      inorder(ptr->left_child, level);
      printf("Level %d:", *level);
      for(i = 1; i <= *level; i++)
         printf("    ");
      printf("%d\n",ptr->key);
      inorder(ptr->right_child, level);
      (*level)--;
   }
}


A Sample run, using the same input sequence as the preorder() traversal is found in the following table.

A sample run (Input Sequence: 60,50,40,70, 80,55,75,65 )
inorder traversal:

Level 3:            40
Level 2:        50
Level 3:            55
Level 1:    60
Level 3:            65
Level 2:        70
Level 4:                75
Level 3:            80



Postorder traversal

The postorder traversal "builds" the tree from the children up to the parents. That is, the left and right children are visited before the contents of the parent node is printed. The code is found in the following table.

void postorder(tree_ptr ptr, int *level)
/* postorder tree traversal */
{
   int i;
   if (ptr) {
      (*level)++;
      postorder(ptr->left_child, level);
      postorder(ptr->right_child, level);
      printf("Level %d:", *level);
      for (i=1; i <= *level; i++)
         printf("    ");
      printf("%d\n",ptr->key);
      (*level)--;
    }
}


A sample run of the postorder() function using the same input sequence as the preorder() function is found in the following table.

A sample run (Input Sequence: 60,50,40,70, 80,55,75,65 )
postorder traversal:

Level 3:            40
Level 3:            55
Level 2:        50
Level 3:            65
Level 4:                75
Level 3:            80
Level 2:        70
Level 1:    60



Level Order traversal

The code for the preorder, inorder, and postorder traversals is simply because each makes use of the system stack. In contrast, the code for the level order traversal is complex because we must create a queue structure and the functions used with it.

The queue definitions

The queue dfinitions are found in the following table. Notice that we have two data fields. The first field holds the tree node; the second holds the level number.

The queue definitions
typedef	struct queue *queue_ptr;
typedef	struct queue  {
	  tree_ptr  key;
	  int level;
	  queue_ptr link;
      };
      
void add_queue(queue_ptr *, queue_ptr *, tree_ptr, int);
void delete_queue(queue_ptr *, tree_ptr *, int *);


The add_queue() function requires four parameters. The first two parameters represent pointers to the front and rear pointers. The third parameter represents the tree node, and the last represents the level.

The delete_queue() function requires three parameters. The first represents the pointer to the front pointer, the second represents the tree node, and the last represents the level

The add_queue() function

The add_queue() function is conceptually identical to the public member functions you created in C++. Unlike C++, where we can hide the front and rear pointers from the public, we must pass these pointers in C. The code is found in the following table.

The add_queue() function
void add_queue(queue_ptr *front, queue_ptr *rear, tree_ptr node, int level)
/*  add a tree ptr to the queue */
{
  queue_ptr temp = (queue_ptr) malloc(sizeof(struct queue));
  if IS_FULL(temp) {
    fprintf(stderr, "The memory is full\n");
    exit(1);
  }
  temp->key = node;
  temp->level = level;
  temp->link = NULL;
  if (!*front)
     *front = temp;
  else
      (*rear)->link = temp;
  *rear = temp;
}


The delete_queue() function

The delete_queue() function is conceptually similar to the public data member you created in your C++ queue class. However, we must pass the front pointer. The code is:

The delete_queue() Function
void delete_queue(queue_ptr *front, tree_ptr *ptr, int *level)
/* delete a tree ptr from the queue */
{
   queue_ptr temp;
   tree_ptr node;

   temp = *front;
   if (*front) {
      node = temp->key;
      *level = temp->level;
      *front = temp->link;
      *ptr = node;
      free(temp);
    }
}


The level_order() traversal

The level_order() function uses the following algorithm:
  1. The front and rear queue pointers are set to null.
  2. The pointer to the root node is added to the queue. This node is at level one.
  3. A loop is established that:
    • Deletes a node from the queue as long as the queue contains nodes
    • Prints the level and the node's key, IF the node is not NULL.
    • Adds the node's left and right children to the queue, IF these children are not NULL
The code is found in the following table.

void level_order(tree_ptr root)
/* level_order tree traversal */
{
   queue_ptr front, rear;
   tree_ptr node;
   int i, level;
   front = rear = NULL;
   add_queue(&front,&rear, root, 1);
   do {
      delete_queue(&front, &node,&level);
      if (node) {
        printf("Level: %d", level);
        for (i=1; i<= level; i++)
           printf("    ");
	printf("%d\n",node->key);
	if(node->left_child)
	   add_queue(&front,&rear,node->left_child,level+1);
	if (node->right_child)
	   add_queue(&front,&rear,node->right_child, level+1);
      }
    }  while (front);
}


A sample run using the same input sequence as the stack algorithms is found in the following table.

A sample run (Input sequence: 60,50,40,70, 80,55,75,65)
level_order traversal:

Level: 1    60
Level: 2        50
Level: 2        70
Level: 3            40
Level: 3            55
Level: 3            65
Level: 3            80
Level: 4                75