tail recursion

What is Tail Recursion? Provide an example and a simple explanation.
Here we provide a simple tutorial and example of a normal non-tail recursive solution to the Factorial problem in Java, and then we can also go over the same problem but use a tail recursive solution in Python. Don’t worry if you don’t know Python, the code is very intuitive and easy to understand even if you come from another background.


If you read our recursion tutorial, then you should already have a good idea of how the recursive solution for the factorial works. This is the Java code that will calculate the factorial of a number:

public int factorial (int x)
{
  if (x == 1)
  {
    return 1;  //base case
  }

  else  /*recursive case*/
   return factorial(x-1) * x;
}
Let’s say that we want to calculate the factorial of 4 using the function above. So, what exactly happens when the value of 4 is passed into the function above? Well, here is the sequence of calls that will be made – with the first to last going from top to bottom:

factorial (4) ;
return factorial(3) * 4;
return factorial(2) * 3;
return factorial(1) * 2;
return 1;
 

 
The thing that you should pay special attention to is the fact that in order to reach the final value of the factorial (which is 24), each and every function call must be executed to completion. You should be able to see that – in order to know the factorial of 4 we must find the factorial of 3 multiplied by 4, and in order to get the factorial of 3, we must get the factorial of 2 multiplied by 3, and in order to get the factorial of 2 we must get the factorial of 1 multiplied by 2, and finally, we know that the factorial of 1 is equal to 1 so 1 is returned because that is our base case which will actually stop the recursion. Then, a 2 will be returned (factorial(1) * 2 is equal to 2), and then a 6 will be returned by factorial(2) *3 and so on and so forth until the final value “24” is returned by the function.

The concept that we are trying to emphasize here is that every function call must run to completion in order for us to finally get to the correct value of “24”. This is different from tail recursion, and you will see why once we go over a variation of the factorial function that uses tail recursion.

Tail Recursion Factorial Implementation in Python
It’s much easier to understand tail recursion with an actual example followed by an explanation of that example. With that in mind, let’s go over an example of a Factorial solution in Python that uses tail recursion instead of normal recursion. We use Python because it’s easier for the sake of illustrating our example.

Example of Tail Recursion
def factorial(i, current_factorial=1):
  if i == 1:
    return current_factorial
  else:
    return factorial(i - 1, current_factorial * i)
Note that we provide a default argument of 1 for the current_factorial, but that only applies to the very first call of the function. When the factorial function is called recursively the default argument is overridden with whatever value is passed by the recursive call. We need to have that second argument there because it will hold the current factorial value which we intend on passing into the function.

Breaking down our example of tail recursion
Now, let’s suppose again that we want to calculate the factorial of 4. Note that the very first call to factorial(4) really is factorial(4, 1), because we have a default argument of 1 for the second parameter in factorial. So, we would end up with a series of calls that look like this:

factorial(4, 1)
return factorial (3,  1 * 4 ) //i == 4
return factorial (2,  4 *3 ) //i == 3
return factorial (1,  12 *2 ) //i == 2
return 24  //i == 1
So, you can see that each time the factorial function is called in our example, a new value for the current_factorial variable is passed into the factorial function. The function is basically updating the current_factorial variable with each call to the function. We are able to maintain the current factorial value because this function accepts 2 arguments/parameters – not just 1 like our normal, non-tail recursive factorial function above.

The difference between tail recursion and normal recursion
You can see that once we make a call to factorial (1, 12 *2 ), it will return the value of 24 – which is actually the answer that we want. The most important thing to notice about that is the fact that all of the recursive calls to factorial (like factorial (2, 4 *3 ), factorial (3, 1*4 ), etc) do not actually need to return in order to get the final value of 24 – you can see that we actually arrive at the value of 24 before any of the recursive calls actually return. So, we can say that a function is tail recursive if the final result of the recursive call – in this case 24 – is also the final result of the function itself, and that is why it’s called “tail” recursion – because the final function call (the tail-end call) actually holds the final result. If we compare that with our earlier example of “normal” recursion, you should see the difference – the “normal” recursive call is certainly not in it’s final state in the last function call, because all of the recursive calls leading up to the last function call must also return in order to actually come up with the final answer.


 
Tail recursion and stack frames
If you read our Recursion Tutorial, then you understand how stack frames work, and how they are used in recursion. We won’t go into detail here since you can just read that article, but basically each recursive call in a normal recursive function results in a separate stack frame as you can see in this graphic which assumes a call of Factorial(3) is being made:

Tail call optimization explanation
Tail call optimization is the process by which a tail recursive function call is optimized to use just one stack frame. You should definitely read our detailed explanation on tail call optimization here: Tail call optimization.

Tail recursion versus normal recursion
You can see that once a final call to the tail recursive factorial is made – the “factorial (2, 3 *2 )”, it will return the value of 6 – which is actually the answer that we want. The most important thing to notice about that is the fact that all of the recursive calls to factorial (like factorial (3, 1 * 3 ), factorial (2, 3 *2 ), etc) do not actually need to return in order to get the final value of 6 – you can see that we actually arrive at the value of 6 before any of the recursive calls actually return. So, we can say that a function is tail recursive if the final result of the recursive call – in this case 6 – is also the final result of the function itself, and that is why it’s called “tail” recursion – because the final function call (the tail-end call) actually holds the final result. If we compare that with our earlier example of “normal” recursion, you should see the difference – the “normal” recursive call is certainly not in it’s final state in the last function call, because all of the recursive calls leading up to the last function call must also return in order to actually come up with the final answer.

Tail call optimization explanation
In our tail recursive example, the recursive calls to factorial do not actually need a new stack frame for each and every recursive call that is made. This is because the calculation is made within the function parameters/arguments – and the final function call actually contains the final result, and the final result does not rely on the return value of each and every recursive call. However, an important point that you should make sure you understand is that it is up to the compiler/interpreter of the particular language to determine whether or not the recursive calls in a tail recursive function actually use an extra stack frame for each recursive call to the function.


 
Some language’s compilers choose to optimize tail recursive functions because they know that the final result will be in the very last function call. So, the compilers will not create a new stack frame for each recursive call, and will instead just re-use the same stack frame. This saves memory overhead by using a constant amount of stack space, which is why it is called tail recursion optimization. But, keep in mind that some compilers do not perform this optimization on tail recursive functions, which means that the tail recursive function will be run like a normal recursive function and will use a new stack frame for each and every function call.

Python does not use tail recursive optimization
In our example of tail recursion, we used Python because it made it easy to illustrate our example. But, it’s interesting to note that Python does not actually utilize tail recursive optimization, which means that it treats tail recursive calls just as it would treat normal recursive calls. This of course means that tail recursive calls in Python will be less efficient then they would be if they were optimized, but there are valid reasons the creator of Python decided not to add this feature.

Tail Recursion versus Iteration
Tail recursion can be as efficient as iteration if the compiler uses what is known as tail recursion optimization. If that optimization is not used, then tail recursion is just as efficient as normal recursion.


 
Is Tail Recursion optimization automatic?

No, tail recursion optimization is a feature that must be built in as part of the compiler, as we mentioned before.

Tail recursion optimization and stack overflow
Because tail recursion optimization essentially makes your tail recursive call equivalent to an iterative function, there is no risk of having the stack overflow in an optimized tail recursive function.

Tail Recursion optimization in Java
Tail recursion optimization is not actually supported by Java because the Java standard does not require that tail recursion be supported, although there are some JVM implementations that do support it as an add-on. But, tail recursion itself (note that we left out the “optimization” part) is supported in Java because it is just a special case of normal recursion – so there’s really nothing extra that Java JVM has to do in order to support tail recursion versus normal recursion. Because there is no tail recursion optimization in Java, code that uses tail recursion will behave like a normal recursive call, and will use multiple stack frames.

Tail call optimization versus tail call elimination
Both tail call optimization and tail call elimination mean exactly the same thing and refer to the same exact process in which the same stack frame is reused by the compiler, and unnecessary memory on the stack is not allocated.

What does TRO stand for in computer science?
TRO stands for Tail recursion optimization.

What does TCE stand for in computer science?
TCE stands for Tail recursion elimination.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值