Properties
Let's go through those points in a meaningful order:- Tree: nodes, edges, root, free of cycles, yadayada.
- I expect trees to have a root node (root).
- I expect nodes to have a container full of sub nodes (subs).
- I assume the order of sub nodes to be of no importance. (See at the end for details and remedies.)
- It works for unidirectional trees, although bi-directionality certainly doesn't hinder it.
- It makes no assumptions on the arity of the trees. Every node may have variable and potentially unlimited number of sub nodes.
- It does not use any information in or for the nodes, like flags, tags or additional pointers to pre- or post-order nodes.
- Traversal: The algorithm visits each node in those trees once. Visiting as in processing, not as in touching, because nodes may need to be touched multiple times.
- Depth-First: Each visiting branch is examined to the deepest nodes before backtracking up the tree.
- Post-Order: The algorithm visits all of a node's sub nodes (and their sub nodes, ...) before visiting the node itself.
- Iterative: As opposed to recursive. The algorithm doesn't need recursion but a loop and data structures.
- O(n): The algorithm takes linear time to complete with regards to the number of nodes. This also requires the helping data structures to be sufficiently fast. Especially when called inside the main loop of the algorithm, the data structures need to provide access in O(1).
Algorithm
Here goes the algorithm. It is easy enough to understand, once it is written in plain sight. Generally speaking, the algorithm touches every node twice:- Once on the way down to examine it and push its sub nodes onto the stack.
- Once on the way up to visit it.
traverse(tree, visit)
todo : stack
if tree.root ≠ null
todo.push([tree.root, down])
while todo.empty = false
todo : stack
if tree.root ≠ null
todo.push([tree.root, down])
while todo.empty = false
node, direction = todo.pop()
if node.subs.empty = true or direction = up
visit(node)
else
todo.push([node, up])
foreach sub in node.subs
todo.push([sub, down])
if node.subs.empty = true or direction = up
visit(node)
else
todo.push([node, up])
foreach sub in node.subs
todo.push([sub, down])
That's it. If the programming language of your choice doesn't support pairs or makes them difficult to handle, here is another implementation that uses two stacks which, combined, can be interpreted as those up and down flags. But it is not as plainly to understand.
traverse(tree, visit)
depth : stack
todo : stack
if tree.root ≠ null
todo.push(tree.root)
while todo.empty = false
depth : stack
todo : stack
if tree.root ≠ null
todo.push(tree.root)
while todo.empty = false
node = todo.top
if node.subs.empty = true or (depth.empty = false and depth.top = node)
visit(node)
if depth.empty = false and depth.top = node
depth.pop()
todo.pop()
else
depth.push(node)
foreach sub in node.subs
todo.push(sub)
if node.subs.empty = true or (depth.empty = false and depth.top = node)
visit(node)
if depth.empty = false and depth.top = node
depth.pop()
todo.pop()
else
depth.push(node)
foreach sub in node.subs
todo.push(sub)