Presentation is loading. Please wait.

Presentation is loading. Please wait.

Binary Tree Traversals

Similar presentations


Presentation on theme: "Binary Tree Traversals"— Presentation transcript:

1 Binary Tree Traversals

2 Binary Tree Traversals
To traverse a data structure is to “visit” each element of the structure – if we consider a baseball diamond as a data structure, for example, a batter who hits a homerun then traverses the bases What we do when we visit an element during a traversal will be application dependent Perhaps, if the purpose of the traversal were to compute the average score of all the students in CS315, we'd compute a running total of all the student scores, adding to it as we visited each student record If the purpose were to convince our CS315 instructor that our traversal algorithm worked properly, we might just print out some identifying data from each element What we'll be concerned with here is the theory and practice of traversal; we'll ignore the purpose of the visit itself

3 Traversal Order Sometimes a traversal order, the order in which the nodes are visited during the traversal, can be pretty obvious Given the linked list below, the only obvious traversal order is: 18, 34, -22, 7, and 341 start 18 34 -22 7 341

4 Traversal Order (cont'd)
Sometimes a good traversal order either won't be obvious at all Or, although the definition may be obvious, the means for accomplishing it may not – alphabetic order is easy to define, for example, but it's not obvious at all how we would actually do that for the binary tree, above D K C L F E J

5 Traversal Order (cont'd)
There are many possible traversal orders for binary trees, we'll look at three common ones for now; we'll see another one later in the course Pre-order In-Order Post-Order The definitions for these three traversal orders are recursive, taking advantage of the fact that the definition of a binary tree itself is recursive

6 Recursive Definition of the Pre-Order Traversal of a Binary Tree
If the tree is not empty: Visit the root Pre-order traverse the left sub-tree Pre-order traverse the right sub-tree D K C L F E J Let's run through a pre- order traversal of the binary tree, above, keeping track of the nodes visited along the way and the order in which they are visited Having just visited node 'C', we're ready to do a pre-order traversal of it's left sub-tree; but we can't since it's empty … At this point, after visiting node 'D', we have completed the pre-order traversal of the tree rooted at 'D', since both it's left and right sub trees are empty Furthermore, since it was the right subtree of the node rooted at 'C', we have finished a complete pre-order traversal of that tree (the one rooted at 'C') as well … … So we'll move on and try to pre- order traverse the right sub-tree (of node 'C') Order the nodes are visited during a pre-order traversal: J E C D

7 Recursive Definition of the Pre-Order Traversal of a Binary Tree
If the tree is not empty: Visit the root Pre-order traverse the left sub-tree Pre-order traverse the right sub-tree …so we can now move on to pre-order traverse the right subtree of 'J' 1 …so we have just completed this step for 'J'… D K C L F E J 2 6 3 5 After the vacuous pre-order traversals of the empty left and right subtrees of 'F', we have completed the pre-order traversal of the tree rooted at 'F' which is the right subtree of 'E' so we have now completed the preorder traversal of the tree rooted at 'E' … 7 4 The order in which a node is visited during a pre-order traversal is sometimes called its pre-order number or pre- order marking …which is the left subtree of 'J'… … so now we can pre- order traverse its right subtree … which means we have completed a pre-order traversal of the left subtree of the tree rooted at 'E' … Order the nodes are visited during a pre-order traversal: J E C D F L K

8 In-Order and Post-Order Traversals
For in-order and post-order, the definitions are similar to pre-order; but we change where we actually do the visit In-order: If the tree is not empty: In-order traverse the left sub-tree Visit the root In-order traverse the right sub-tree Post-order: If the tree is not empty: Post-order traverse the left sub-tree Post-order traverse the right sub-tree The in-order visit is done in between the in-order traversals of the subtrees The post-order visit is done after (post) the post-order traversals of the subtrees

9 In-Order Traversal of a Binary Tree
We start the traversal at the root; that's always where everything starts with binary trees But that doesn't mean we can visit that root just yet; we have to in- order traverse its left subtree first If the tree is not empty: In-order traverse the left sub-tree Visit the root In-order traverse the right sub-tree … so we can now visit 'J' and start to in- order traverse its right subtree Can't visit 'L' yet, have to in-order traverse it's left subtree first Since 'C' doesn't have a left subtree, the in-order traversal of its left subtree is vacuous and we can visit 'C' and then in-order traverse its right subtree We can visit 'K' immediately, since it has no left subtree D K C L F E J … and can now visit 'L' Since 'K' has no right subtree, we're all done with the subtree rooted at 'K' … and since 'F's right subtree is empty we're done with it and hence done with the subtree rooted at 'E', which is the entire left subtree of 'J' … Can't visit 'E' yet either We have to in-order traverse its left subtree first And we've completed an in-order traversal of the binary tree rooted at 'J' By visiting 'D', and (vacuously) completing an in-order traversal of its right subtree, we've completed the in- order traversal of the tree rooted at 'C', which is the left subtree of 'E' … We can visit 'F' right away… So now we can visit 'E' and set to work doing an in-order traversal of its right subtree Since 'D' has an empty left subtree, we can visit it ('D') as soon as we get here Now what? Order the nodes are visited during an in-order traversal: C D E F J K L

10 Post-Order Traversal If the tree is not empty:
Post-order traverse the left sub-tree Post-order traverse the right sub-tree Visit the root D K C L F E J Order the nodes are visited during a post-order traversal: D C F E K L J

11 Remember: The Three Traversal Orders Are Quite Different
K C L F E J Pre-order: J E C D F L K In-order: C D E F J K L Post-order : D C F E K L J Technically, what we've defined here are left-to-right traversals Definitions for the their right-to-left analogues are pretty obvious, but pretty pointless: the right-to-left traversals are rarely, if ever, used for anything – although I think there's a cute theorem that proves that the left-to-right pre-order and right-to-left post order traversals yield mirror images of each other, if you care for that sort of thing (well, I do myself, actually, but then I thought a Ph.D. in Computer Science would be a good idea, so that should give you some sort of calibration on my warped sense of aesthetics) 99.99% of the time, a reference to just in-order traversal will mean a left-to-right in-order traversal

12 Summary So Far We've defined three abstract properties of a binary tree (any binary tree), called traversal orders Pre-order In-order Post-order Every binary tree has these three abstract traversal orders (and others as well, for that matter); whether or not any given one of them is actually useful for anything is a separate issue Each one is, in fact, exceedingly useful under the right circumstances – we'll be using them all throughout the rest of the semester Algorithms to actually do one of these traversals depend on how one chose to implement the binary tree itself

13 Implementation of Binary Trees
Probably the most common (but most definitely not the only) implementation for binary trees involves left and right pointers Here's a sample definition in C: struct binaryTreeNode { some_type theData; struct binaryTreeNode *left, *right; }; And here's a depiction of the resulting structure: Note: As we actually declared it, above, a C compiler would probably lay it out like so: Usually, we don't care about the internal layout of our structures (the compiler handles such things) and the original depiction, to the left, makes it easier to draw nice binary tree pictures in class; but it's important not to be too cavalier with the real truth, hence this note theData theData If, for some odd reason (I can't imagine why), it were important to have it actually laid out as in the picture to the left, you should be able to figure out how to alter the struct binaryTreeNode declaration here to achieve the layout you want (you weird creature, you ;-)

14 Implementation of Traversals
Given that the definitions of the three most common traversal orders were recursive and that the C language permits recursion, it's pretty simple to write a recursive function in C to actually do one of these traversals; here's an example: typedef struct binaryTreeNode { char theData; struct binaryTreeNode *leftPtr, *rightPtr; } BTN; void inOrderTraverse( BTN *ptrToSomeBinaryTree ) if ( ptrToSomeBinaryTree != NULL ) inOrderTraverse(ptrToSomeBinaryTree->leftPtr); /* Visit the root here */ inOrderTraverse(ptrToSomeBinaryTree->rightPtr); } But for CS315 we'll look at some other traversal techniques as well. Why? Academic tradition: I suffered; so you'll suffer ;-)

15 A Non-Recursive Pre-Order Traversal Algorithm
Those checks, the two new ‘if’ statements, are actually redundant with this one, since the first thing our recursive function does here is check to make sure it's not being asked to traverse an empty tree, so we don't really need to check before we call it (our recursive function), which is what the two new ‘if’ statements do… Our tree is not empty in this example, so we can get started by visiting its root If the tree is not empty: Visit the root If the left child is not empty, Pre-order traverse the left sub-tree Pre-order traverse the right sub-tree If the right child is not empty, Since the left child of 'C' is empty, we'll move on and try the right child Let's first review the animation for our recursive algorithm again; but let's add an annotation to indicate a node where we've started traversing the left subtree but haven't yet started traversing the right subtree Let's also make a harmless but apparently pointless modification to this algorithm Let's stop here and see if we can get a simple non-recursive algorithm to duplicate the behavior to this point But putting the new ‘if’ statements in here will shortly make it easier to see the correspondence between our recursive code and our non-recursive code, which definitely will need these ‘if’ statements OK, now let's run through our algorithm again 'J' is an incompletely processed node at this point; we'll have to get back here later somehow in order to traverse its right subtree J A E C D K F L A node in whose left subtree we are currently working; so we haven't begun to work its right subtree yet Visited: J E C D K F

16 A Non-Recursive Pre-Order Traversal (cont'd)
First we initialize trvPtr trvPtr = root; while (trvPtr != NULL) visit(trvPtr); if (trvPtr->left != NULL ) trvPtr = trvPtr->left; else if (trvPtr->right != NULL) trvPtr = trvPtr->right; else trvPtr = NULL; If the tree is not empty: Visit the root If the left child is not empty, Pre-order traverse the left sub-tree If the right child is not empty, Pre-order traverse the right sub-tree Here's where we see the change from recursion to iteration, but the meaning is clearly the same Since the trvPtr node has no left subtree, we'll move on to the next branch of our 'if' statement and check for a right subtree to traverse Note that after setting trvPtr = trvPtr->left, we're done with the 'if' statement so we're done with one pass through the 'while' loop That will always be true: Inside the traversal loop, we only do two things: Visit the trvPtr node Advance the trvPtr node according to the 'if' statement root So far everything has been going swimmingly with our simple-minded non-recursive algorithm, but now, for the first time we have to backtrack We've finished somebody's left subtree, one of our annotated nodes, and so are ready to work its right subtree but have no way to get back to it from where we are now (trvPtr is NULL) The first part of the non-recursive algorithm will look pretty similar to the recursive one, actually, but uses two pointers: root, which points to the original tree, which we won't change; it's just our starting point trvPtr, which will be the node we're currently working on Note: I've used indentation to show scope groupings for readability here, but to save vertical space, I've left out the {braces} that are actually required; other than that, this mostly is real code … which will terminate the 'while' loop trvPtr So let's go through the first part of our traversal again, watching how the non-recursive algorithm parallels the recursive one J trvPtr A Since the trvPtr node has no children, after the visit, we set trvPtr to NULL… E trvPtr C trvPtr D trvPtr K trvPtr F L trvPtr A node in whose left subtree we are currently working; so we haven't begun to work its right subtree yet Visited: J E C D K F

17 A Non-Recursive Pre-Order Traversal Algorithm (cont'd)
trvPtr = root; while (trvPtr != NULL) visit(trvPtr); if (trvPtr->left != NULL ) trvPtr = trvPtr->left; else if (trvPtr->right != NULL) trvPtr = trvPtr->right; else trvPtr = NULL; If the tree is not empty: Visit the root If the left child is not empty, Pre-order traverse the left sub-tree If the right child is not empty, Pre-order traverse the right sub-tree root Now that we've seen the close correspondence between the two forms of the traversal algorithm for at least the first part of our traversal, let's get rid of the recursive version and give ourselves more room to work on the non-recursive one to solve its “back-tracking” problem J A E C D K F L trvPtr A node in whose left subtree we are currently working; so we haven't begun to work its right subtree yet Visited: J E C D K F

18 A Non-Recursive Pre-Order Traversal Algorithm (cont'd)
So let's add two more lines here so that after falling out of the 'while' loop, we'll return to a node whose right subtree we need to traverse … trvPtr = root; while (trvPtr != NULL) visit(trvPtr); if (trvPtr->left != NULL ) … and somehow restart this traversal loop and continue on our way J E D trvPtr = trvPtr->left; K insertIntoAux(trvPtr); else if (trvPtr->right != NULL) trvPtr = trvPtr->right; else trvPtr = NULL; We want to get back to one of our annotated nodes; they're the ones for which we haven't done the right subtree's traversal yet But although our animation provided the visual annotation we see in the picture to the right, our algorithm didn't actually keep track of them What we should have done is stored their addresses into some auxiliary data structure just before we moved away from them Two obvious (and very much related) questions arise here: Which node do we actually want to remove and set trvPtr equal to? What type of structure must we use for aux so that we always remove the correct node, the one whose right subtree we want to traverse next? aux With the line we just added, the pictures to the right show the situation after we've fallen out of the while loop after setting trvPtr = NULL The aux data structure contains nodes we aren't done with yet, since we still need to traverse their right subtrees trvPtr ??? root Here's what we want to wind up with, an auxiliary data structure of some sort that contains pointers to our incompletely traversed nodes, so we can do something like: trvPtr = removeFromAux() to get back to a “deferred” node we still need to work on trvPtr = removeFromAux(); trvPtr = trvPtr->right; Let's modify our code in the more or less obvious fashion to insert a pointer to the current node into an auxiliary dynamic data structure before moving away from it (the current, incompletely traversed, node) J A E … and then move down to the root of that right subtree … C D K F L trvPtr A node in whose left subtree we are currently working; so we haven't begun to work its right subtree yet Visited: J E C D K F

19 A Non-Recursive Pre-Order Traversal Algorithm (cont'd)
trvPtr = root; while (trvPtr != NULL) visit(trvPtr); if (trvPtr->left != NULL ) D J E K J E Now, when the left subtree is empty, we'll just move down to the right If trvPtr->right is not NULL, that will cause us to continue in our loop and traverse the right subtree of the current node If it is NULL, our 'while' loop will terminate and we'll pop the stack to backtrack to a node we deferred earlier D K push(trvPtr); insertIntoAux(trvPtr); Note that although 'K' was the last node inserted into aux, we need it to be the first one removed And after 'K', we'll want to try to traverse the right subtree of 'D' before trying the right subtree of 'E', before finally trying the right subtree of 'J' It looks like whenever we want to get back to a deferred node still waiting for work, the one we want to go back to is the one we actually deferred most recently, not the oldest So aux must be a Last-In-First-Out (LIFO) data structure, otherwise known as a stack trvPtr = trvPtr->left; else trvPtr = trvPtr->right; else if (trvPtr->right != NULL) trvPtr = trvPtr->right; else trvPtr = NULL; aux root trvPtr = removeFromAux(); trvPtr = pop(); trvPtr ??? J trvPtr = trvPtr->right; Starting to look pretty simple, isn't it? Let's run through it and see if it still works right A And this highlighted logic here, although correct, can be simplified a bit We wrote it like this (else if … else …) originally to emphasize the parallel with our recursive algorithm; but if trvPtr->right is NULL, we want trvPtr to wind up NULL anyway, so that we can exit from our 'while' loop and pop the stack So the else if … check here is unnecessary; we simply want to move down to the right, regardless of whether or not that's NULL E Well, overall, we want to wind up visiting the nodes of this tree in the order JECDKFLA, since that's the pre-order enumeration of the nodes Since we just visited 'F' before we fell out of the 'while' loop; it's 'L' we want to visit next, so … C ... it's 'K' whose right subtree we want to start traversing now D K F L A node in whose left subtree we are currently working; so we haven't begun to work its right subtree yet Visited: J E C D K F

20 A Non-Recursive Pre-Order Traversal Algorithm (cont'd)
trvPtr = root; while (trvPtr != NULL) visit(trvPtr); if (trvPtr->left != NULL) push(trvPtr); trvPtr = trvPtr->left; else trvPtr = trvPtr->right; K D E J trvPtr = pop(); trvPtr=trvPtr->right; root trvPtr D C E K L F A J Looks pretty good so far, but now we have to start the traversal loop again, starting from node 'L' We could always add a goto statement, of course, but there's got to be a better way, right? A node in whose left subtree we are currently working; so we haven't begun to work its right subtree yet Visited: J E C D K F

21 A Non-Recursive Pre-Order Traversal Algorithm (cont'd)
… we return to check the loop condition of the the outer loop to see if we're done … int done = 0; /* Boolean, controls the outer loop */ while ( ! done ) trvPtr = root; while (trvPtr != NULL) visit(trvPtr); if (trvPtr->left != NULL ) push(trvPtr); trvPtr = trvPtr->left; else trvPtr = trvPtr->right; What we need to do here, after exiting from the inner loop, is check to see if the stack is empty before popping the stack and restarting the inner loop If the stack is empty, we're all done with the entire traversal so we want to terminate the outer loop; if not, we'll continue on by popping the stack and moving down into some node's right subtree Good idea anyway, no? Since it keeps our code from blowing up if it tries to pop the stack when it is empty ;-) D … and if not, we'll start the inner (traversal) loop again E aux J trvPtr = pop(); trvPtr = trvPtr->right; root if ( stackEmpty() ) done = 1; /* We're done */ else /* Not done */ J A So when are we, in fact, done? Why, when there are no more deferred nodes whose right subtrees still haven't been traversed And how do we know when we have no more deferred nodes? Why, when the stack is empty, of course! Now let's figure out: How to restart the traversal loop, and How to terminate the whole traversal The two questions are actually closely related E C To “restart” something is to repeat it, no? So how do we repeat things in C? Why, we put them inside a loop, of course … and you don't have to take the next mid-term if you figure out why the modifications to do a post-order traversal are more extensive than the really rather trivial one to do the in-order Now, after exiting from the inner loop, popping the stack, and moving down into the right subtree … D Your next homework asignment will involve modifying this algorithm to do an in-order traversal instead of a pre- order one … That's it, the whole algorithm, a stack-based pre-order traversal of a binary tree I leave it as an exercise to the student to step through the algorithm from where we are now (trvPtr points to 'L' and we're about to restart the inner loop) to see that it completes properly K F L trvPtr A node in whose left subtree we are currently working; so we haven't begun to work its right subtree yet Visited: J E C D K F


Download ppt "Binary Tree Traversals"

Similar presentations


Ads by Google