0

I have had some real difficulty in understanding the core of this snippet though it looks very simple and straightforward. Given a simple Binary tree, this famous function produces a mirrored result of the same tree.

void mirror(struct node* node) {
  if (node==NULL) {
    return;
  }
  else {
    struct node* temp;

    // do the subtrees
    mirror(node->left); //this part confuses me
    mirror(node->right); //so does this 

    // swap the pointers in this node
    temp = node->left;
    node->left = node->right;
    node->right = temp;
  }
}

This will be the code. I have two questions on my mind after reading this code.

  1. mirror(node->left) and mirror(node->right) places two consecutive function calls on the call stack. My understanding is, first, the function is called on the left node and then right. This process recurs as long as node is empty. But when it is not empty what does it return? Does it just come back to the call statement without returning anything?

  2. How are these functions invoked from the call stack? It would be really helpful if someone runs through the function calls and tells how and why do we need two consecutive function calls.

P.S: Why aren't iterations used in this scope?

EDIT: quoting a useful comment by EOF, is it possible to tail-recurse this function by moving the swapping before the two recursive calls?

Tania
  • 1,855
  • 1
  • 15
  • 38
  • You are trying to get your head around "recursion". [This Q&A](http://stackoverflow.com/q/3021/) might help. Or not. – Nemo Apr 28 '15 at 17:21
  • Is it just me, or could you get tail-recursion by swapping the left- and right `node*`s *first*? – EOF Apr 28 '15 at 17:29
  • ^ now this is another question! And, a good one. – Tania Apr 28 '15 at 17:30
  • If you perform the swap before the recursion then the *second* recursive call would be a candidate for tail-recursion optimization. The first recursive call can never a candidate, because that call is not the function's last action (at minimum, the second recursive call follows it). – John Bollinger Apr 28 '15 at 17:43
  • @JohnBollinger: Sure, but even tail-recursing *one* of the calls causes gcc to produce *vastly* better code. For fun, you could first mirror the left child, then swap the children, and finally mirror the left child again, tail-recursively! – EOF Apr 28 '15 at 17:44
  • @EOF: sure, I'm not arguing about that. I just want to be sure Tania understands the scope of the tail-recursion optimization. – John Bollinger Apr 28 '15 at 17:48
  • @EOF So how would I perform a Tail call optimization on the above code? If i call mirror left and swap and mirror-left again, Only one stack frame will be allocated for my function(mirror(left->child)) and is that what you mean by TR optimization? – Tania Apr 29 '15 at 05:17

2 Answers2

2
  1. mirror(node->left) and mirror(node->right) places two consecutive function calls on the call stack.

That's an odd way to put it, and it may reflect a misunderstanding. At no one time does the call stack ever contains a representation of both those function calls.

My understanding is, first, the function is called on the left node and then right. This process recurs as long as node is empty. But when it is not empty what does it return?

The function has type void. It never returns anything.

Does it just come back to the call statement without returning anything?

Yes, when the function finishes, control resumes with the next statement after the function call.

  1. How are these functions invoked from the call stack?

Functions are not invoked "from" the call stack. Stack frames are created for functions and pushed as part of the process of calling the function. The details are unspecified; compilers are responsible for generating code to do the right thing.

It would be really helpful if someone runs through the function calls and tells how and why do we need two consecutive function calls.

To mirror the whole tree means to reverse the children of every node. When processing a given node, the function must therefore ensure that that node's children are both mirrored. It accomplishes that by performing a recursive call for each one, and afterward swaps them.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • So is there a way to iteratively do this? Why dont we choose the iterative approach here? – Tania Apr 28 '15 at 17:34
  • By the way great explanation. Answers my question and its sub-questions. :) – Tania Apr 28 '15 at 17:35
  • 1
    The code is much more compact with a recursive traversal. The trade off is that you are using up stack space which can be a problem if the recursion goes too deep. Not usually a problem with tree traversal, however. – pens-fan-69 Apr 28 '15 at 17:35
  • 1
    @Tania, you can do the job iteratively, but that requires an additional data structure to track the path from the root of the tree to the node currently being processed. In the recursive version, the call stack handles that implicitly. The recursive version is also considerably shorter and (says me) easier to understand. – John Bollinger Apr 28 '15 at 17:38
  • @JohnBollinger with respect to the "easier to understand," I think you would find few who disagreed after seeing the two approaches side by side. – pens-fan-69 Apr 28 '15 at 17:43
1

So what is happening here is that the mirror() function is performing the mirror operation in post order. That is, the left sub-tree is processed, then the right sub-tree is processed, then the parent node. So the first thing it does is check whether the current node is null.

if (node == null) {
    return;
}

This will happen only after a leaf node is reached. A leaf node has null left and right pointers, thus it will effectively execute mirror(null) when it executes the mirror(node->left). Likewise with the right node. Since the node is null (e.g. this is the left or right pointer of a leaf node), there is nothing to do.

The calls to mirror(node->left) and mirror(node->right) cause the left and right child sub-trees to be processed. After that is complete, the left and right pointers are swapped in the current node and the mirror process is complete.

pens-fan-69
  • 979
  • 7
  • 11
  • I get that. By processing a mirror(node->left) call when node is not null, what will my function return? node->left? – Tania Apr 28 '15 at 17:25
  • 1
    The function returns nothing. The return type is `void`. – Dark Falcon Apr 28 '15 at 17:27
  • As @DarkFalcon said, there is nothing to return. This function does all of its work internally and there is no return value that the caller is looking to do something with. In this case, the return in the if structure is merely to stop execution of the function and return to the caller. – pens-fan-69 Apr 28 '15 at 17:28
  • Oh is it just a way to get back to the point where the call originated and execute the next statement? – Tania Apr 28 '15 at 17:31
  • 1
    Correct. As it turns out, the return is not really necessary in this case. Rather than saying if(node == null) {return} else {do stuff} the code could simply say if(node != null) {do stuff} and the effect would be the same. – pens-fan-69 Apr 28 '15 at 17:33
  • Wow thanks. Is there a way to iteratively do this than use recursion? – Tania Apr 28 '15 at 17:35
  • @Tania I won't reproduce the code here, but it is much uglier. You can see comparison of pre-, in-, and post-order tree traversal with both the iterative and recursive approaches here: http://www.lixinglian.com/idea/?p=340 – pens-fan-69 Apr 28 '15 at 17:40