pythoniterationbinary-search-treetree-traversalpostorder

Iterative postorder traversal of a binary tree with a single stack, how to approach the problem?


I have been studying up on algorithms and data structures and I wrote a post-order traversal for a binary tree without using recursion and using only one stack.

Here is the code:

def postorder_iterative(self):
    current = self
    s = []
    current1 = None
    done = 0
    def peek(s):
        return s[-1]

    while(not done):
        if current and (current != current1):
            s.append(current)
            current = current.leftChild
        elif(len(s) > 0):
            if peek(s).rightChild and peek(s).rightChild != current:
                current = peek(s).rightChild
            else:
                current = current1 = s.pop()
                print(current.key)
        else:
            done = 1

This code actually works but it took me forever to come up with it.

Can someone explain what is the intuitive way of thinking about this problem?

I'd like to be able to reproduce it using logic and not spend as much time as I did on it.


Solution

  • Post-order traversal requires that you only print the current node value after traversing both the left and right subtrees. You are using the stack to traverse the left tree only, and use the current1 variable (the last node printed) to know that you are now backing out of a right-hand side tree so you can print the current node.

    I'd rename current to node, current1 to last (for last printed), remove the peek() function to just reference stack[-1] directly as tos (top of stack), and simplify your approach to:

    def postorder_iterative(self):
        node, last = self, None
        stack = []
    
        while True:
            if node and node is not last:
                # build up the stack from the left tree
                stack.append(node)
                node = node.leftChild
            elif stack:
                # no more left-hand tree to process, is there a right-hand tree?
                tos = stack[-1]
                if tos.rightChild and tos.rightChild is not node:
                    node = tos.rightChild
                else:
                    # both left and right have been printed
                    node = last = stack.pop()
                    print(last.key)
            else:
                break
    

    It is still hard to follow what is going on however, as the connection between last and the point where the left and right subtrees have been processed isn't all that clear.

    I'd use a single stack with a state flag to track where in the process you are:

    def postorder_iterative(self):
        new, left_done, right_done = range(3)   # status of node
        stack = [[self, new]]  # node, status
    
        while stack:
            node, status = stack[-1]
            if status == right_done:
                stack.pop()
                print(node.key)
            else:
                stack[-1][1] += 1 # from new -> left_done and left_done -> right_done
                # new -> add left child, left_done -> add right child
                next_node = [node.leftChild, node.rightChild][status]
                if next_node is not None:
                    stack.append((next_node, new))
    

    Nodes go through three states, simply by incrementing the state flag. They start as new nodes, then progress to left, then right, and when the top of the stack is in that last state we remove it from the stack and print the node value.

    When still in the new or left states, we add the left or right node, if present, to the stack as a new node.

    Another approach pushes the right-hand tree onto the stack before the current node. Then later, when you return to the current node, having taken it from the stack, you can detect the case where you still need to process the right-hand side because the top of the stack will have the right-hand node. In that case you swap the top of the stack with the current node and continue from there; you'll later return to the same place and will no longer have that right-hand side node on the top of the stack so you can print:

    def postorder_iterative(self):
        stack = []
        node = self
    
        while node or stack:
            while node:
                # traverse to the left, but add the right to the stack first
                if node.rightChild is not None:
                    stack.append(node.rightChild)
                stack.append(node)
                node = node.leftChild
    
            # left-hand tree traversed, time to process right or print
            node = stack.pop()
    
            if stack and node.rightChild is stack[-1]:
                # right-hand tree present and not yet done, swap tos and node
                node, stack[-1] = stack[-1], node
            else:
                print(node.key)
                node = None