Trees

Definitions

A tree is either null or a node followed by 0 or more trees

  1. root
  2. Start of the tree. This is a relative term in that the child of a root node is the root node of a tree below it.
  3. node
  4. element in a tree, as in lists
  5. forest
  6. a collection of trees
  7. leaf
  8. node with no subtrees
  9. children
  10. subtrees of a node
  11. parent
  12. node above a given node
  13. siblings
  14. nodes with same parents
  15. level
  16. root is zero
    1 + level(parent)
    sometimes, the level is defined to start at 1
    
  17. height
  18. max level of tree
  19. order
  20. width of a tree

A tree is a subset of a general connected structure called a graph. A node in a tree may have many children, but it has only one parent. A list is a tree of order 1.

Uses

  1. family tree
  2. file systems
  3. cataloging (Dewey decimal)
  4. org charts
  5. sorting and searching
Example
struct node {
            char * value;
            node * left;
            node * mid;
            node * right;
        };

This tree is of order 3. This is like a linked list where each node can point to three other nodes instead of one. All we know about linked lists still applies although altered.

Traversals

Going through a tree is called a traversal. This is a description of a path through each node in a tree. This is the same as the techniques we developed to look at every node in a list. Three common binary tree traversals are:

  1. preorder
  2. root left right
  3. inorder
  4. left root right
  5. postorder
  6. left right root

get class to suggest how to do these first.

In these routines, the function doroot() indicates the location in the code where we should put the statements or functions that are to be performed on the node we are looking at.

Examples expression tree example
TraversalNotationResults
preorderprefix+ c * a b
postorderpostfix (RPN)c a b * +
inorderinfixc + a * b

Discuss pre, post and inorder notation, and conversion stack actions to evaluate post instead of inorder

Iterative Traversals

The code to implement a traversal looks like the description.

	void
	print(node *t)
	{
		if(t == NULL) return();
		print(t->left);
		cout << t->value;
		print(t->right);
	} // print

The routines above are recursive, it is possible to traverse a tree iteratively but is more difficult. Below is a routine that performs an inorder traversal of a tree without recursion.

inorder(T)
	ptr = T
	repeat
		while(ptr <> nil)
			push(ptr)
			ptr = ptr^.left
		endwhile
		if (not stack_empty()) then
			x = pop()
			print(x^.value)
			ptr = ptr^.right
		endif
	until (stack_empty() and (ptr = nil))

Ordered Binary Trees

The algorithms that we will be discussing will concern a particular type of tree called a binary tree which is of order 2. And we are looking at a variant on binary trees that are ordered in the following way:

            val1
        val2        val3

In this tree, for every node, val2 < val1 < val3. This ordering is only a useful convention, it is not part of the definition of binary trees.

Example

struct node {
            char * value;
            node * left;
            node * right;
        };

Tree Search

Searching an ordered binary tree is like searching a linear list and like a binary search on an array. If the node we are looking at is not the one we want, then if the searched for value is less than the one we are looking at, it must be to the left of it and to the right if it is greater than the current node.

Example Ordered Binary Tree ordered tree example This tree results from inserting the nodes from data that is in this order: 8,7,15,3,10,18.

Search through this tree for 15 and then for 20. Note the value and location of T when the 20 is not found.

Tree Search And Insertion

bool
search(node * t, char * str)
{

    if(t == NULL) return(false);
        
    int c = strcmp(str,t->value);
    if(c == 0)  return(true);  // found it
    if ( c > 0) 
        return(search(t->right,str));
    else 
         return(search(t->left,str));
} // search

The algorithm presented above has the interesting property of leaving us at the location where a new node is to be inserted if it is not already in the tree. So a simple change will produce a search and insert algorithm.

bool
insert(node * &t,char * str)
{
    node * n;

    if(t == NULL) {
        if( (n = new node) == NULL) 
           throw new Mem("insert: node");
        if( (n->value = new char[strlen(str)+1]) == NULL) 
           throw new Mem("insert: string");
        (void) strcpy(n->value,str);
        n->left = n->right = NULL;
        t = n;
        return(true);
     }
     else {
        int c = strcmp(str,t->value);
         if(c == 0)  {
             return(true);
         }
         else
              if ( c > 0) {
                 return(insert(t->right,str));
              }
              else {
                 return(insert(t->left,str));
              }
     }  // t not NULL
} // insert

Walk through what happens when an attempt is made to add a 20 to the tree above. Don't immediately connect the new node to the position in the 18 node. Let the class figure out why the connection is made.

The fact that T is passed in by reference is critical to this routine. When the new(T) is executed for node 20, the value (right hand value) of T is NULL. The location of T (left hand value) is the right child field of the 11 node.

The next routine can be used to print the tree on its side. It mostly illustrates the use of a second parameter to hold a value and count the number of levels of recursion.

void
treeprint(node * t, int l)
{
   if(t != NULL) {
      treeprint(t->right,l+1);
      for(int i=0; i< l; i++) cout << "  ";
      cout << t->value << endl;
      treeprint(t->left,l+1);
   }
} // treeprint

Tree Deletion

Deleting an element from an ordered binary tree is more difficult. There are several cases to deal with.

  1. A leaf node
  2. This is the simplest case. Simply set the pointer to it in its parent to nil. Example, delete 3.
  3. A non-leaf node with one child
  4. Here we replace the pointer to the deleted node in its parent with the pointer to the child. Example, delete 7.
  5. A non-leaf node with two children
  6. This is the tricky case. Here we must rotate one of the children into the parent and delete the child. This is the case that is dealt with by calling the subroutine del(). It finds the right most leaf node on the left side of the node to be deleted.