What is tail recursion?

前端 未结 28 3651
一个人的身影
一个人的身影 2020-11-21 05:03

Whilst starting to learn lisp, I\'ve come across the term tail-recursive. What does it mean exactly?

相关标签:
28条回答
  • 2020-11-21 05:11

    A tail recursion is a recursive function where the function calls itself at the end ("tail") of the function in which no computation is done after the return of recursive call. Many compilers optimize to change a recursive call to a tail recursive or an iterative call.

    Consider the problem of computing factorial of a number.

    A straightforward approach would be:

      factorial(n):
    
        if n==0 then 1
    
        else n*factorial(n-1)
    

    Suppose you call factorial(4). The recursion tree would be:

           factorial(4)
           /        \
          4      factorial(3)
         /             \
        3          factorial(2)
       /                  \
      2                factorial(1)
     /                       \
    1                       factorial(0)
                                \
                                 1    
    

    The maximum recursion depth in the above case is O(n).

    However, consider the following example:

    factAux(m,n):
    if n==0  then m;
    else     factAux(m*n,n-1);
    
    factTail(n):
       return factAux(1,n);
    

    Recursion tree for factTail(4) would be:

    factTail(4)
       |
    factAux(1,4)
       |
    factAux(4,3)
       |
    factAux(12,2)
       |
    factAux(24,1)
       |
    factAux(24,0)
       |
      24
    

    Here also, maximum recursion depth is O(n) but none of the calls adds any extra variable to the stack. Hence the compiler can do away with a stack.

    0 讨论(0)
  • 2020-11-21 05:13

    Tail Recursion is pretty fast as compared to normal recursion. It is fast because the output of the ancestors call will not be written in stack to keep the track. But in normal recursion all the ancestor calls output written in stack to keep the track.

    0 讨论(0)
  • 2020-11-21 05:14

    The recursive function is a function which calls by itself

    It allows programmers to write efficient programs using a minimal amount of code.

    The downside is that they can cause infinite loops and other unexpected results if not written properly.

    I will explain both Simple Recursive function and Tail Recursive function

    In order to write a Simple recursive function

    1. The first point to consider is when should you decide on coming out of the loop which is the if loop
    2. The second is what process to do if we are our own function

    From the given example:

    public static int fact(int n){
      if(n <=1)
         return 1;
      else 
         return n * fact(n-1);
    }
    

    From the above example

    if(n <=1)
         return 1;
    

    Is the deciding factor when to exit the loop

    else 
         return n * fact(n-1);
    

    Is the actual processing to be done

    Let me the break the task one by one for easy understanding.

    Let us see what happens internally if I run fact(4)

    1. Substituting n=4
    public static int fact(4){
      if(4 <=1)
         return 1;
      else 
         return 4 * fact(4-1);
    }
    

    If loop fails so it goes to else loop so it returns 4 * fact(3)

    1. In stack memory, we have 4 * fact(3)

      Substituting n=3

    public static int fact(3){
      if(3 <=1)
         return 1;
      else 
         return 3 * fact(3-1);
    }
    

    If loop fails so it goes to else loop

    so it returns 3 * fact(2)

    Remember we called ```4 * fact(3)``

    The output for fact(3) = 3 * fact(2)

    So far the stack has 4 * fact(3) = 4 * 3 * fact(2)

    1. In stack memory, we have 4 * 3 * fact(2)

      Substituting n=2

    public static int fact(2){
      if(2 <=1)
         return 1;
      else 
         return 2 * fact(2-1);
    }
    

    If loop fails so it goes to else loop

    so it returns 2 * fact(1)

    Remember we called 4 * 3 * fact(2)

    The output for fact(2) = 2 * fact(1)

    So far the stack has 4 * 3 * fact(2) = 4 * 3 * 2 * fact(1)

    1. In stack memory, we have 4 * 3 * 2 * fact(1)

      Substituting n=1

    public static int fact(1){
      if(1 <=1)
         return 1;
      else 
         return 1 * fact(1-1);
    }
    

    If loop is true

    so it returns 1

    Remember we called 4 * 3 * 2 * fact(1)

    The output for fact(1) = 1

    So far the stack has 4 * 3 * 2 * fact(1) = 4 * 3 * 2 * 1

    Finally, the result of fact(4) = 4 * 3 * 2 * 1 = 24

    The Tail Recursion would be

    public static int fact(x, running_total=1) {
        if (x==1) {
            return running_total;
        } else {
            return fact(x-1, running_total*x);
        }
    }
    
    
    1. Substituting n=4
    public static int fact(4, running_total=1) {
        if (x==1) {
            return running_total;
        } else {
            return fact(4-1, running_total*4);
        }
    }
    

    If loop fails so it goes to else loop so it returns fact(3, 4)

    1. In stack memory, we have fact(3, 4)

      Substituting n=3

    public static int fact(3, running_total=4) {
        if (x==1) {
            return running_total;
        } else {
            return fact(3-1, 4*3);
        }
    }
    

    If loop fails so it goes to else loop

    so it returns fact(2, 12)

    1. In stack memory, we have fact(2, 12)

      Substituting n=2

    public static int fact(2, running_total=12) {
        if (x==1) {
            return running_total;
        } else {
            return fact(2-1, 12*2);
        }
    }
    

    If loop fails so it goes to else loop

    so it returns fact(1, 24)

    1. In stack memory, we have fact(1, 24)

      Substituting n=1

    public static int fact(1, running_total=24) {
        if (x==1) {
            return running_total;
        } else {
            return fact(1-1, 24*1);
        }
    }
    

    If loop is true

    so it returns running_total

    The output for running_total = 24

    Finally, the result of fact(4,1) = 24

    0 讨论(0)
  • 2020-11-21 05:14

    Many people have already explained recursion here. I would like to cite a couple of thoughts about some advantages that recursion gives from the book “Concurrency in .NET, Modern patterns of concurrent and parallel programming” by Riccardo Terrell:

    “Functional recursion is the natural way to iterate in FP because it avoids mutation of state. During each iteration, a new value is passed into the loop constructor instead to be updated (mutated). In addition, a recursive function can be composed, making your program more modular, as well as introducing opportunities to exploit parallelization."

    Here also are some interesting notes from the same book about tail recursion:

    Tail-call recursion is a technique that converts a regular recursive function into an optimized version that can handle large inputs without any risks and side effects.

    NOTE The primary reason for a tail call as an optimization is to improve data locality, memory usage, and cache usage. By doing a tail call, the callee uses the same stack space as the caller. This reduces memory pressure. It marginally improves the cache because the same memory is reused for subsequent callers and can stay in the cache, rather than evicting an older cache line to make room for a new cache line.

    0 讨论(0)
  • 2020-11-21 05:16

    Here is a quick code snippet comparing two functions. The first is traditional recursion for finding the factorial of a given number. The second uses tail recursion.

    Very simple and intuitive to understand.

    An easy way to tell if a recursive function is a tail recursive is if it returns a concrete value in the base case. Meaning that it doesn't return 1 or true or anything like that. It will more than likely return some variant of one of the method parameters.

    Another way is to tell is if the recursive call is free of any addition, arithmetic, modification, etc... Meaning its nothing but a pure recursive call.

    public static int factorial(int mynumber) {
        if (mynumber == 1) {
            return 1;
        } else {            
            return mynumber * factorial(--mynumber);
        }
    }
    
    public static int tail_factorial(int mynumber, int sofar) {
        if (mynumber == 1) {
            return sofar;
        } else {
            return tail_factorial(--mynumber, sofar * mynumber);
        }
    }
    
    0 讨论(0)
  • 2020-11-21 05:17

    Consider a simple function that adds the first N natural numbers. (e.g. sum(5) = 1 + 2 + 3 + 4 + 5 = 15).

    Here is a simple JavaScript implementation that uses recursion:

    function recsum(x) {
        if (x === 1) {
            return x;
        } else {
            return x + recsum(x - 1);
        }
    }
    

    If you called recsum(5), this is what the JavaScript interpreter would evaluate:

    recsum(5)
    5 + recsum(4)
    5 + (4 + recsum(3))
    5 + (4 + (3 + recsum(2)))
    5 + (4 + (3 + (2 + recsum(1))))
    5 + (4 + (3 + (2 + 1)))
    15
    

    Note how every recursive call has to complete before the JavaScript interpreter begins to actually do the work of calculating the sum.

    Here's a tail-recursive version of the same function:

    function tailrecsum(x, running_total = 0) {
        if (x === 0) {
            return running_total;
        } else {
            return tailrecsum(x - 1, running_total + x);
        }
    }
    

    Here's the sequence of events that would occur if you called tailrecsum(5), (which would effectively be tailrecsum(5, 0), because of the default second argument).

    tailrecsum(5, 0)
    tailrecsum(4, 5)
    tailrecsum(3, 9)
    tailrecsum(2, 12)
    tailrecsum(1, 14)
    tailrecsum(0, 15)
    15
    

    In the tail-recursive case, with each evaluation of the recursive call, the running_total is updated.

    Note: The original answer used examples from Python. These have been changed to JavaScript, since Python interpreters don't support tail call optimization. However, while tail call optimization is part of the ECMAScript 2015 spec, most JavaScript interpreters don't support it.

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