Trees

Definitions

A tree is either null or a node followed by 0 or more trees 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

Example
struct tree {
int value;
struct tree *child1;
struct tree *child2;
struct tree *child3;
};

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: 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
  +    
c     *
    a   b
Traversal Notation Results
preorder prefix + c * a b
postorder postfix (RPN) c a b * +
inorder infix c + a * b
Discuss pre, post and inorder notation, and conversion stack actions to evaluate post instead of inorder

Iterative Traversals

The routines above are recursive, it is possible to traverse a tree iteratively but is more difficult. Below are two routines the perform an inorder traversal of a tree without recursion.
inorder(T)
ptr = T
do
while(ptr != nil) {
push(ptr)
ptr = ptr->left
}
if (not stack_empty()) {
x = pop()
print(x->value)
ptr = ptr->right
}
until (stack_empty() && (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 bin {
int value;
struct bin *rchild;
struct bin *lchild;
};
 

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
    8    
  7     15  
3     10   18
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

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.

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 T=makenode() is executed for node 20, the value (right hand value) of T is nil. The location of T (left hand value) is the right child field of the 11 node.

After the walk through of adding the 20 node, present the addchar() solution to insertion in a linked list as an example of the same kind of thing but without recursion or trees. This is insert2() in "Linkstrings". The critical element in this solution is the use of pass by reference.
Procedure addchar(var L,ch)
{ add the char ch to the front of the list L }
1. new(fresh)
2. fresh^.value = ch
3. fresh^.next = L
4. L = fresh
end add char

Procedure insert{L,target,ch)
{ add the character ch to the list L after target }
1. z = findchar(L,target)
2. if z <> nil then
3. addchar(z^.next,ch)
4. else
5. error(no list)
6. endif
7. end insert

Since at line 3, z points to the node we want to add the ch after, z->next is the location that should point to the new node and while alterations to z are not carried out of the routine, changes to the thing z points to are permanent. From the point of view of the addchar routine, whatever is passed into it as the L variable is the front of a list, it alters it appropriately. So even though the L variable is in this case pointing to an element somewhere in the middle of a list, it puts the element on the front of the list it is given, even though the pointer passed in points to the middle of the list. 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.

Tree Deletion

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

Tree Algorithms, Part 1

These algorithms have to do with binary trees. The record structure used to build them is below followed by routines to traverse the tree, search, search and insert and print the tree.
 

struct treenode {
int value;
struct treenode *lchild;
struct treenode *rchild;
};
 

Traversals

 > void pre(T) void post(T)  void in(T)
1. if(T == NULL) if(T == NULL)  if(T == NULL)
2.    return;    return ;    return;
3. else { else {  else {
4.    doroot(T);     post(T->left);    in(T->left);
5.    pre(T->left);     post(T->right);    doroot(T);
6.    pre(T->right);    doroot(T);    in(T->right)
7. }

Tree search

struct treenode *
search(struct treenode *T,int val) {
/* returns either NULL if it doesn't find it or */
/* a pointer to the node containing val. */

1. if(T == NULL)
2.    return(NULL);
3. else
4.    if(T->value == val)
5.       return(T);
6.    else
7.       if(val < T->value)
8.          return(search(T->left,val));
9.       else
10.       return(search(T->right,val));
} /* search */
 

Tree search with insertion


struct treenode * insert(struct treenode **T,int val){
/* this searchs T for val and if it doesn't find */
/* it, it creates a node for it and returns a pointer */
/* to it. T is passed by reference */

1. if(*T == NIL)
2.    *T=createnode(); /* make a new tree node */
3.    *T->value = val;
4.    *T->left = T->right = NIL;
5.    return(*T);
6. else
7.    if(*T->value == val)
8.       return(*T);
9.    else
10.      if(val < *T->value)
11.         return(insert(*T->left,val));
12.      else
13.         return(insert(*T->right,val));
} /* insert */
 

Tree printing


void treeprint(struct treenode *T,int L) {
/* this prints the tree T sideways showing it's structure. */
/* L is a helping variable to keep a count of the indentation to print */

int i;
1. if(T != NIL)
2.    treeprint(T->left,L+1);
3. for(i = 1; i<=L;i++)
4.    printf(" ");
5. end for
6. printf("%d\n",T->value); /* prints with a newline */
7. treeprint(T->right,L+1);
} /* treeprint */
 

Tree Algorithms, Part 2

This continues from Trees 1 and presents an algorithm to delete nodes from a binary tree. The tree node is described by the record below.
struct treenode {
int value;
struct treenode *lchild;
struct treenode *rchild;
};
 

Binary Tree Deletion


struct treenode *Hold;
void delete (int val,struct treenode **T) {
/* Hunt through T for the node containing val and delete it.
Use a local procedure called del().
*/

1. if(*T == NULL)
2.    printf("it is not here\n")
3. else
4.    if(val < *T->value)
5.       delete(val,*T->left);
6.    else
7.       if(val > *T->value)
8.          delete(val,*T->right);
9.       else {
10.          Hold = *T; /* remember where we are */
11.          if(Hold->right == NULL)
12.             *T = Hold->left; /* move left child pointer up */
13.          else
14.             if(Hold->left == NULL)
15.                *T = Hold->right; /* move right child pointer up */
16.             else
17.                del(Hold->left);
18.      }
19. } /* delete */

void del(struct treenode **r){
1. if(r->right != NULL)
2.    del(r->right);
3. else {
4.    Hold->value = r->value;
5.    r = r->left;
6. }
7. } /* del */
 

Tree Algorithms, Part 3

This is an iterative version of the binary tree insertion algorithm. This is most closely related to the search-insert routine in the recursive tree routines handout, Trees 1. This particular implementation is from "Applied Data Structures Using Pascal", Guy J. Hale, Richard J. Easton, D. C. Heath and Company,1987.
void inserttree(struct treenode *root,int val)
/* iterative binary tree insertion */
1. if((newnode=createnode()) == NULL) {
       printf("No space for new node\n");
       return;
    }
2. newnode->value = val;
3. newnode->rchild = newnode->lchild = NULL;
4. inserted = FALSE;
5. top = root;
6. if(root = NULL)
7.    root = newnode;
8. else {
9.     while(!inserted){
10.       if(val < top->value)
11.          if(top->lchild != NULL)
12.             top = top->lchild;
13.          else {
14.             top->lchild = newnode;
15.             inserted = TRUE;
16.          }
17.       else
18.         if(top->rchild != NULL)
19.             top = top->rchild;
20.          else {
21.              top->rchild = newnode;
22.              inserted = TRUE;
23.          }
25.     }/* endwhile */
26. }
27. } /* insertree */