Understanding how recursive functions work

后端 未结 18 789
野性不改
野性不改 2020-11-22 07:08

As the title explains I have a very fundamental programming question which I have just not been able to grok yet. Filtering out all of the (extremely clever) \"In order to

相关标签:
18条回答
  • 2020-11-22 07:31

    Many of the answers above are very good. A useful technique for solving recursion though, is to spell out first what we want to do and code as a human would solve it . In the above case, we want to sum up a sequence of consecutive integers (using the numbers from above):

    2, 3, 4, 5  //adding these numbers would sum to 14
    

    Now, note that these lines are confusing (not wrong, but confusing).

    if (a > b) {
        return 0 
    }
    

    Why the test a>b?, and whyreturn 0

    Let's change the code to reflect more closely what a human does

    func sumInts(a: Int, b: Int) -> Int {
      if (a == b) {
        return b // When 'a equals b' I'm at the most Right integer, return it
      }
      else {
        return a + sumInts(a: a + 1, b: b)
      }
    }
    

    Can we do it even more human like? Yes! Usually we sum up from left to right (2+3+...). But the above recursion is summing from right to left (...+4+5). Change the code to reflect it (The - can be a little intimidating, but not much)

    func sumInts(a: Int, b: Int) -> Int {
      if (a == b) {
        return b // When I'm at the most Left integer, return it
      }
      else {
        return sumInts(a: a, b: b - 1) + b
      }
    }
    

    Some may find this function more confusing since we are starting from the 'far' end, but practicing can make it feel natural (and it is another good 'thinking' technique: Trying 'both' sides when solving a recursion). And again, the function reflects what a human (most?) does: Takes the sum of all left integers and adds the 'next' right integer.

    0 讨论(0)
  • 2020-11-22 07:32

    A little bit off-topic, I know, but... try looking up recursion in Google... You'll see by example what it means :-)


    Earlier versions of Google returned the following text (cited from memory):

    Recursion

    See Recursion

    On September 10th 2014, the joke about recursion has been updated:

    Recursion

    Did you mean: Recursion


    For another reply, see this answer.

    0 讨论(0)
  • 2020-11-22 07:33

    You might be interested in Nisan and Schocken's implementation of functions. The linked pdf is part of a free online course. It describes the second part of a virtual machine implementation in which the student should write a virtual-machine-language-to-machine-language compiler. The function implementation they propose is capable of recursion because it is stack-based.

    To introduce you to the function implementation: Consider the following virtual machine code:

    enter image description here

    If Swift compiled to this virtual machine language, then the following block of Swift code:

    mult(a: 2, b: 3) - 4
    

    would compile down to

    push constant 2  // Line 1
    push constant 3  // Line 2
    call mult        // Line 3
    push constant 4  // Line 4
    sub              // Line 5
    

    The virtual machine language is designed around a global stack. push constant n pushes an integer onto this global stack.

    After executing lines 1 and 2, the stack looks like:

    256:  2  // Argument 0
    257:  3  // Argument 1
    

    256 and 257 are memory addresses.

    call mult pushes the return line number (3) onto the stack and allocates space for the function's local variables.

    256:  2  // argument 0
    257:  3  // argument 1
    258:  3  // return line number
    259:  0  // local 0
    

    ...and it goes-to the label function mult. The code inside mult is executed. As a result of executing that code we compute the product of 2 and 3, which is stored in the function's 0th local variable.

    256:  2  // argument 0
    257:  3  // argument 1
    258:  3  // return line number
    259:  6  // local 0
    

    Just before returning from mult, you will notice the line:

    push local 0  // push result
    

    We will push the product onto the stack.

    256:  2  // argument 0
    257:  3  // argument 1
    258:  3  // return line number
    259:  6  // local 0
    260:  6  // product
    

    When we return, the following happens:

    • Pop the last value on the stack to the memory address of the 0th argument (256 in this case). This happens to be the most convenient place to put it.
    • Discard everything on the stack up to the address of the 0th argument.
    • Go-to the return line number (3 in this case) and then advance.

    After returning we are ready to execute line 4, and our stack looks like this:

    256:  6  // product that we just returned
    

    Now we push 4 onto the stack.

    256:  6
    257:  4
    

    sub is a primitive function of the virtual machine language. It takes two arguments and returns its result in the usual address: that of the 0th argument.

    Now we have

    256:  2  // 6 - 4 = 2
    

    Now that you know how a function call works, it is relatively simple to understand how recursion works. No magic, just a stack.

    I have implemented your sumInts function in this virtual machine language:

    function sumInts 0     // `0` means it has no local variables.
      label IF
        push argument 0
        push argument 1
        lte              
        if-goto ELSE_CASE
        push constant 0
        return
      label ELSE_CASE
        push constant 2
        push argument 0
        push constant 1
        add
        push argument 1
        call sumInts       // Line 15
        add                // Line 16
        return             // Line 17
    // End of function
    

    Now I will call it:

    push constant 2
    push constant 5
    call sumInts           // Line 21
    

    The code executes and we get all the way to the stopping point where lte returns false. This is what the stack looks like at this point:

    // First invocation
    256:  2   // argument 0
    257:  5   // argument 1
    258:  21  // return line number
    259:  2   // augend
    // Second
    260:  3   // argument 0
    261:  5   // argument 1
    262:  15  // return line number
    263:  3   // augend
    // Third
    264:  4   // argument 0
    265:  5   // argument 1
    266:  15  // return line number
    267:  4   // augend
    // Fourth
    268:  5   // argument 0
    269:  5   // argument 1
    270:  15  // return line number
    271:  5   // augend
    // Fifth
    272:  6   // argument 0
    273:  5   // argument 1
    274:  15  // return line number
    275:  0   // return value
    

    Now let's "unwind" our recursion. return 0 and goto line 15 and advance.

    271:  5
    272:  0
    

    Line 16: add

    271:  5
    

    Line 17: return 5 and goto line 15 and advance.

    267:  4
    268:  5
    

    Line 16: add

    267:  9
    

    Line 17: return 9 and goto line 15 and advance.

    263:  3
    264:  9
    

    Line 16: add

    263:  12
    

    Line 17: return 12 and goto line 15 and advance.

    259:  2
    260:  12
    

    Line 16: add

    259:  14
    

    Line 17: return 14 and goto line 21 and advance.

    256:  14
    

    There you have it. Recursion: Glorified goto.

    0 讨论(0)
  • 2020-11-22 07:34

    One really good tip I came across in learning and really understanding recursion is to spend some time learning a language that doesn't have any form of loop construct other than via recursion. That way you'll get a great feel for how to USE recursion via practice.

    I followed http://www.htdp.org/ which, as well as being a Scheme tutorial, is also a great introduction on how to design programs in terms of the architecture and design.

    But basically, you need to invest some time. Without a 'firm' grasp of recursion certain algorithms, such as backtracking, will always seem 'hard' or even 'magic' to you. So, persevere. :-D

    I hope this helps and Good Luck!

    0 讨论(0)
  • 2020-11-22 07:34

    Let me tell you with an example of Fibonacci series, Fibonacci is

    t(n) = t(n - 1) + n;

    if n = 0 then 1

    so let see how recursion works, I just replace n in t(n) with n-1 and so on. it looks:

    t(n-1) = t(n - 2) + n+1;

    t(n-1) = t(n - 3) + n+1 + n;

    t(n-1) = t(n - 4) + n+1 + n+2 + n;

    .

    .

    .

    t(n) = t(n-k)+ ... + (n-k-3) + (n-k-2)+ (n-k-1)+ n ;

    we know if t(0)=(n-k) equals to 1 then n-k=0 so n=k we replace k with n:

    t(n) = t(n-n)+ ... + (n-n+3) + (n-n+2)+ (n-n+1)+ n ;

    if we omit n-n then:

    t(n)= t(0)+ ... + 3+2+1+(n-1)+n;

    so 3+2+1+(n-1)+n is natural number. it calculates as Σ3+2+1+(n-1)+n = n(n+1)/2 => n²+n/2

    the result for fib is : O(1 + n²) = O(n²)

    This the best way to understand recursive relation

    0 讨论(0)
  • 2020-11-22 07:36

    1.The function is called recursively until a condition is met. That condition is a > b. When this condition is met, return 0. At first glance, I would expect the return value to be 0 which is obviously incorrect.

    Here is what the computer computing sumInts(2,5) would think if it were able to:

    I want to compute sumInts(2, 5)
    for this, I need to compute sumInts(3, 5)
    and add 2 to the result.
      I want to compute sumInts(3, 5)
      for this, I need to compute sumInts(4, 5)
      and add 3 to the result.
        I want to compute sumInts(4, 5)
        for this, I need to compute sumInts(5, 5)
        and add 4 to the result.
          I want to compute sumInts(5, 5)
          for this, I need to compute sumInts(6, 5)
          and add 5 to the result.
            I want to compute sumInts(6, 5)
            since 6 > 5, this is zero.
          The computation yielded 0, therefore I shall return 5 = 5 + 0.
        The computation yielded 5, therefore I shall return 9 = 4 + 5.
      The computation yielded 9, therefore I shall return 12 = 3 + 9.
    The computation yielded 12, therefore I shall return 14 = 2 + 12.
    

    As you see, some call to the function sumInts actually returns 0 however this not the final value because the computer still has to add 5 to that 0, then 4 to the result, then 3, then 2, as described by the four last sentences of the thoughts of our computer. Note that in the recursion, the computer does not only have to compute the recursive call, it also has to remember what to do with the value returned by the recursive call. There is a special area of computer's memory called the stack where this kind of information is saved, this space is limited and functions that are too recursive can exhaust the stack: this is the stack overflow giving its name to our most loved website.

    Your statement seems to make the implicit assumption that the computer forgets what it were at when doing a recursive call, but it does not, this is why your conclusion does not match your observation.

    2.Printing out the value of 'a' on each iteration yields a value which I would expect: 2, 3, 4, 5 (at which point 5+1 > b which meets the first condition: a > b) but I still don't see how the value of 14 is achieved.

    This is because the return value is not an a itself but the sum of the value of a and the value returned by the recursive call.

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