Binary Search Tree Inorder Traversal

后端 未结 2 1299
傲寒
傲寒 2021-01-17 04:38

I am confused by this code:

void in_order_traversal_iterative(BinaryTree *root) {
  stack s;
  BinaryTree *current = root;
  while (!s.emp         


        
相关标签:
2条回答
  • 2021-01-17 04:41

    You're missing the fact that after a node is popped, its right child must still be traversed:

      current = s.top();
      s.pop();
      cout << current->data << " ";
      current = current->right;
    

    If you had only the data on the stack, this would be impossible. The loop invariant is that the stack holds exactly those nodes with un-traversed right children.

    Another way to see what's going on is to transform the recursive traversal to the iterative by algebra:

    traverse(node) {
      if (node) {
        traverse(node->left);
        visit(node);
        traverse(node->right);
      }
    }
    

    First convert the tail call to iteration. We do this by updating the argument and replacing the recursive call with a goto the start of the function:

    traverse(node) {
     start:
      if (node) {
        traverse(node->left);
        visit(node);
        node = node->right;
        goto start;
      }
    }
    

    The goto and if are the same as a while, so we have so far

    traverse(node) {
      while (node) {
        traverse(node->left);
        visit(node);
        node = node->right;
      }
    }
    

    Replacing the other recursive call requires us to simulate the call stack of the compiler's runtime environment. We do that with an explicit stack.

    traverse(node) {
     start:
      while (node) {
        stack.push(node);   // save the value of the argument.
        node = node->left;  // redefine it the same way the recursive call would have
        goto start;         // simulate the recursive call
                            // recursive call was here; it's gone now!
       recursive_return:    // branch here to simulate return from recursive call
        visit(node);
        node = node->right;
      }
      // simulate the recursive return: if stack has args, restore and go to return site
      if (!stack.empty()) {
        node = stack.pop();  // restore the saved parameter value
        goto recursive_return;
      }
    }
    

    Though it's ugly, this is a way that always works to implement iterative versions of recursive code. (It's more complicated if there are multiple non-tail recursive calls, but not much.) And I'm sure you can see the similarity to your code.

    We can even get rid of the ugliness with more algebra. First, it's not hard to see this code:

     start:
      while (node) {
        stack.push(node);   // save the value of the argument.
        node = node->left;  // redefine it the same way the recursive call would have
        goto start;         // simulate the recursive call
    

    when executed beginning with start is equivalent to

      while (node) {
        stack.push(node);   // save the value of the argument.
        node = node->left;  // redefine it the same way the recursive call would have
      }
    

    We can also replace

      if (!stack.empty()) {
        node = stack.pop();  // restore the saved parameter value
        goto recursive_return;
      }
    

    with the following

      if (!stack.empty()) {
        node = stack.pop();  // restore the saved parameter value
        visit(node);
        node = node->right;
        goto start;
      }
    

    We have merely copied the three instructions after recursive_return: into the if body.

    With this, there is no way left to arrive at the recursive_return label, so we can delete it along with the two following statements:

       // Dead code!  Delete me!
       recursive_return:
        visit(node);
        node = node->right;
    

    We now have:

    traverse(node) {
     start:
      while (node) {
        stack.push(node);   // save the value of the argument.
        node = node->left;  // redefine it the same way the recursive call would have
      }
      if (!stack.empty()) {
        node = stack.pop();  // restore the saved parameter value
        visit(node);
        node = node->right;
        goto start;
      }
    }
    

    We can get rid of the last goto start by replacing it with an endless loop:

    traverse(node) {
      loop {
        while (node) {
          stack.push(node);        // save the value of the argument
          node = node->left;       // redefine it the same way the recursive call would have
        }
        if (stack.empty()) break;  // original code returns, so does this!
        node = stack.pop();        // restore the saved parameter value
        visit(node);
        node = node->right;
      }
    }
    

    Note we are returning under the same conditions as the previous code: the stack is empty!

    I will let you prove to yourself that this code does the same as what you presented, only it's a bit more efficient because it avoids some comparisons! We never had to reason at all about pointers and stack elements. It "just happened."

    0 讨论(0)
  • 2021-01-17 05:00

    It's not pushing the whole tree into the stack, it pushes the left-most part of the tree. Then it begin to pop the elements and push their right-most counterparts, in ascending order.

    0 讨论(0)
提交回复
热议问题