Just as we iterate the elements of a list, so may we want to traverse all of the elements in a tree. Sometimes, we traverse a tree just to print it out. Sometimes we traverse a tree to look for a particular value. Sometimes we traverse a tree to compute a value based on the tree.
What values might we compute from a tree? We might simply compute the size of the tree, or the depth of the tree, or the number of times a value appears in the tree. If the tree is not in any particular order, we might also search a tree for a value that meets particular criteria.
At first glance, traversing trees seems straightforward: You simply visit each node in the tree, processing the node as you go. But behind the obvious “process each node” strategy, there are a wide variety of options.
Most nodes have more than one child. Do you process the children in order from left to right, right to left, or leave the order unspecified? The client of your traversal algorithm will want to know (and may even want to choose).
When we think recursively, we think about processing all of one subtree before processing the next subtree. That means we traverse down to a leaf in one subtree before we look at even the topmost node in the other subtree. However, there are times that it makes sense to process all of the nodes at one level before going on to the next level. An algorithm that goes across each level, one at a time, is called a breadth-first algorithm. A traversal algorithm that goes deep into one subtree before processing the next subtree is called a depth-first algorithm. Once again, a client may want to know which approach you will use or choose which approach you will use.
We usually have to visit a node before we visit its children - after all, in many implementations we get the information on the children from the node. However, we can choose different orders in which to process nodes. Two general approaches are preorder, in which we process a node before processing its children and postorder, in which we process a node after processing its children. Preorder processing is also called top-down and postorder processing is also called bottom-up. For binary trees with a depth-first approach, it is also possible to support inorder processing - process the left subtree, process the node, then process the right subtree.
So, when we write algorithms that process trees, we have three decisions to make:
That gives about ten different traversals. Why ten and not twelve? Because inorder breadth-first traversal doesn’t make a lot of sense, given that subtrees are on different levels.
Do these different approaches visit nodes in different orders? Certainly. Let’s look at a simple binary search tree.
e / \ c h / \ / \ a d f j
Before you read on, make a note to yourself how you’d visit the tree in each of the orders given above.
Ready? Fill in the following table.
|Policy||Order Elements are Processed|
|Depth first, Preorder, Left to right|
|Depth first, Preorder, Right to left|
|Depth first, Postorder, Left to right|
|Depth first, Postorder, Right to left|
|Depth first, Inorder, Left to right|
|Depth first, Inorder, Right to left|
|Breadth first, Preorder, Left to right|
|Breadth first, Preorder, Right to left|
|Breadth first, Postorder, Left to right|
|Breadth first, Postorder, Right to left|
Are you done? Here’s what we get.
|Policy||Order Elements are Processed|
|Depth first, Preorder, Left to right||e c a d h f j|
|Depth first, Preorder, Right to left||e h j f c d a|
|Depth first, Postorder, Left to right||a d c f j h e|
|Depth first, Postorder, Right to left||j f h d a c e|
|Depth first, Inorder, Left to right||a c d e f h j|
|Depth first, Inorder, Right to left||j h f e d c a|
|Breadth first, Preorder, Left to right||e c h a d f j|
|Breadth first, Preorder, Right to left||e h c j f d a|
|Breadth first, Postorder, Left to right||a d f j c h e|
|Breadth first, Postorder, Right to left||j f d a h c e|
Wasn’t that fun? Surprisingly, there are use cases for each of the traversal orders.
We indicated that there are some times that it’s useful to use
trees to compute a value. Here’s one of the most common: Computer
scientists often use trees to represent arithmetic expressions.
For example, consider the following might represent the expression
* / \ / \ + - / \ | / \ | 3 4 + / \ / \ 5 6
To evaluate this tree, we need to evaluate both subtrees and then
combine them using the operation. So we evaluate the
subtree and the
-(5+6) and multiply them together. Arithmetic
evaluation normally requires depth-first, postorder evaluation.