Recursion, Trees and Tree Traversal Module CE00352-2 System Programming & Computer Control Systems
Lists Data Pointer Data Pointer Can be used as queue or stack Data Pointer Data Pointer
Tree Structure Self-referential structure Two or more pointers Binary Tree two pointers Left pointer Data Right Pointer
Binary Tree Structure struct treenode { struct treenode *link; int datum; struct treenode *link; };
Binary Tree Insertion and deletion similar to list Traversal performed using a recursive process What is recursion?
Recursion iterative process Uses reusable chunks of code that contain parameters int mult(int a, int b) Parameters in brackets { } return (a*b); total = mult(qty,sizeinmm); Arguments in brackets
Recursion Could call function iteratively total = sizeinmm; while(total < 10000) { total = mult(total,qty); }
Recursion Or alternatively could call itself!! int mult(int a, int b) { if (a < 1000) { result = a*b; return(mult(result,b)) } else { return a; } }
Recursion This is called by: total = mult(qty,sizeinmm); Three things can be seen: The function has a call to itself in its body The function has a trapping statement that checks against a condition to prevent infinite recursion. The result of the recursive process is passed back as a return of the non-recursive branch of the trap.
Recursion The functions, address of the function and constituent data declarations are put on and taken off the stack whilst memory is available. The Operating System employs the stack to keep track of the recursion so that once the process reaches its terminating condition, the stack is unloaded carrying out its calculations etc. as it unravels the recursion.
Factorial A better and more obvious example is to use a recursive function to process factorials. A factorial is a number which multiplies itself by a decrementing value each time using the formula: n* (n-1)*(n-2)*(n-3)*(n-4)..1 So 4! (The! being used to represent a factorial) is the same as: 4*3*2*1
int main() { int i; printf( "%2d! = %ld\n", i, factorial( 4 ) ); return 0; } int factorial( int number ) { if ( number <= 1 ) return 1; else return ( number * factorial( number - 1 ) ); }
General System Stack - return addresses are pushed here each time a function call is made - includes a copy of each local variable and parameter for the function stack stack frame
General System Stack 200 100 Fact: return=100 n=4,result= main stack stack frame
General System Stack 300 200 100 Fact: return=200 n=3,result= Fact: return=100 n=4,result= main stack stack frame
General System Stack 400 300 200 100 Fact: return=300 n=2,result= Fact: return=200 n=3,result= Fact: return=100 n=4,result= main stack stack frame
General System Stack 500 400 300 200 100 Fact: return=400 n=1,result= Fact: return=300 n=2,result= Fact: return=200 n=3,result= Fact: return=100 n=4,result= main stack stack frame 1
General System Stack 400 300 200 100 Fact: return=300 n=2,result=2 Fact: return=200 n=3,result= Fact: return=100 n=4,result= main stack stack frame 2
General System Stack 300 200 100 Fact: return=200 n=3,result=6 Fact: return=100 n=4,result= main stack stack frame 6
General System Stack 200 100 Fact: return=100 n=4,result=24 main stack 24
General System Stack 100 main stack 24 to I/O device
Trees A tree is a non-linear twodimensional data structure two or more links to other nodes. The initial node is referred to as the root Subsequent nodes - Children. Children arranged ordinal arrangement relative to the parent node. A node with no children is referred to as a leaf.
Trees There are many applications for trees. One of the popular uses is the directory structure in many common operating systems:
Binary Trees In a binary tree only 0,1, or 2 children are possible. Each child belongs to a path referred to as left or right
particularly useful for searching efficiently; this is why it is often referred to as a Binary Search Tree. This structure solves the problem of finding a node in a list efficiently and uses the same principle as a binary split search as used in arrays. Searching
Tree structure typedef struct treenode { Item Value; struct treenode *Left; struct treenode *Right; } TreeNode Instead of *NextPointer (in a linked list) it has *Left and *Right pointers
Precedence Each subtree can be considered a tree in its own right Rules of key precedence apply All keys in the left subtree will precede the root key and all keys in the right subtree will exceed root key (where the parent becomes the root Using these rules builds a tree from a number sequence like this: 10, 5, 1, 7, 6,19, 12, 11,15
Adding keys Keys are added by starting at the root node and making comparisons: if key < root then go to left if key > root then go to right The next node is considered a root of a subtree and the process is repeated recursively until the leaf position is found.
Recursive process to add node insertnode (Root, Value) if (bottom of tree) if enough memorycreate new node root->data = Value root->leftpointer = NULL root->rightpointer = NULL else error not enough memory else if (value less than root->value) insertnode(root->leftpointer, Value) else if (value greater than root->value) insertnode(root->rightpointer, Value) else error duplicate key
Trace Rootpointer On calling the insert function the pointer to the root is passed in. The pointer has a value null so a new node is created. Its data becomes 10 its leftpointer is null and its right pointer is null. This is the first and therefore Root node. The root pointer is given a value that corresponds to the address of the root node. Null 10 Null
Trace The next value entered is 5. The address of the root pointer is not null it contains the address of the root. 5 is less than 10 so the recursive call invokes the insert function again and passes in the leftpointer. It is null so a new node is created. The new node has a value 5 and two null pointers. The left pointer of the root node now contains the pointer to the node with the value 5 Null 5 10 Null Null
Trace The next value entered is 1. The root pointer is passed in. It points to the address of the node containing 10. 1 is less than 10 so it looks at the leftpointer. The leftpointer is not null so the function is called again using the address stored in the leftpointer. This points to the node containing 5 so the function is invoked, again using the leftpointer because 1 is less than 5. This time the address stored in the leftpointer is NULL so a leaf node is created and the leftpointer of the previous node stores that address Null 1 5 Null 10 Null Null
Trace The next value entered is 7 it recursively calls the insert function until the node containing 5 is reached. Althought the leftpointer points to an address the rightpointer is the one that is used because 7 is greater than 5. This pointer is Null so a new leaf node is created and the rightpointer of the previous node is changed to store its address Null 1 Null 5 10 Null 7 Null Null
Search To search a tree the same process is used:- start at root, if key = root then found if key < root then go left if key > root then go right
Search // if number in left subtree // search left subtree // else if number in right subtree // search right subtree // otherwise stop TreeNode *TreeSearch (TreeNode *Root, Item Value) { if (Root) { if (Value < Root->Value) Root = TreeSearch (Root->Left, Value); else if (Number > Root->Value) Root = TreeSearch (Root->Right, Value); } return Root; }
Traversing a Tree Three different ways to traverse a tree In-order Pre-order Post-order - NOT reverse order.
Most common traversal algorithm processes nodes in a binary search tree inorder, from the smallest to the largest:-left, Root, Right: Known as In-Order traversal. Tree traversal
Order There are two other ways that can be used however. Pre-order and Post-Order In order Left Root Right Pre-order Root Left Right Post-order Left Right Root
IN-order In the case of an in-order traversal we can print the values stored in the tree in order and the mechanism would be : Node = Root print Node->Left print Node print Node->Right Again a recursive algorithm can be used to traverse the tree making a new root for each node processed. This algorithm must again cater for detection of the leaf node to go back up to the next level. Next we will replace any moves in the tree with a recursive call to the function, pass in Root as a parameter and construct the stopping condition to be when Root == NULL (i.e. when the bottom of the tree is reached).
Print function PrintNodes(TreeNode *Root) { if(root) { PrintNodes(Root->Left); Print(Root); PrintNodes(Root->Right); } } Note how recursion "remembers" the previous state of the search and so the algorithm is able to move back up the tree without building a path list. The recursion builds its own stack of previous function calls that acts as its memory.
Declarations /* Create a binary tree and traverse it in order */ #include <stdio.h> #include <stdlib.h> struct treenode { struct treenode *leftptr; int data; struct treenode *rightptr; }; typedef struct treenode TreeNode; typedef TreeNode *TreeNodePtr;
Main int main() { int i, item; TreeNodePtr rootptr = NULL; /* insert values between 1 and 15 in the tree */ printf( "The numbers being placed in the tree are:\n" ); for ( i = 1; i <= 10; i++ ) { while (1>item<15) { scanf( %d,&item); } printf( "%3d", item ); insertnode( &rootptr, item ); } /* traverse the tree inorder */ printf( "\n\nthe inorder traversal is:\n" ); inorder( rootptr ); return 0; }
void insertnode( TreeNodePtr *treeptr, int value ) { if ( *treeptr == NULL ) { /* *treeptr is NULL */ *treeptr = malloc( sizeof( TreeNode ) ); } if ( *treeptr!= NULL ) { ( *treeptr )->data = value; ( *treeptr )->leftptr = NULL; ( *treeptr )->rightptr = NULL; } else printf( "%d not inserted. No memory available.\n", value ); } else if ( value < ( *treeptr )->data ) insertnode( &( ( *treeptr )->leftptr ), value ); else if ( value > ( *treeptr )->data ) insertnode( &( ( *treeptr )->rightptr ), value ); else printf( "dup" ); INSERT NODE
Traverse tree in order void inorder( TreeNodePtr treeptr ) { if ( treeptr!= NULL ) { inorder( treeptr->leftptr ); printf( "%3d", treeptr->data ); inorder( treeptr->rightptr ); } }