4

Can anybody explain how the following code works. The code was given as a fast recursive implementation of a function returning n-th Fibonacci number. I have a general idea of how recursive functions work. I can fully understand the direct recursive implementation of such function, using the definition of Fibonacci numbers, which, however, is not efficient. The main thing I cannot grasp is what does fib(n – 1, prev0) return when we have garbage stored in prev0.

int fib(int n, int &prev1) {
    if (n < 2) {
        prev1 = 0;
        return n;
    }
    int prev0;
    prev1 = fib(n – 1, prev0);
    return prev0 + prev1;
}

I am a beginner, so, please, be as specific as you can.

8
  • 2
    Why do you think the code shouldn't work? Commented Sep 8, 2015 at 18:50
  • 1
    What in particular you don't understand? Elaborate in your question please. Commented Sep 8, 2015 at 18:52
  • This function is fast because it only recurses on one path. Commented Sep 8, 2015 at 18:53
  • The code should work (it is not something I have written), I just cannot grasp the algorithm behind it. I do understand the general idea though, that it tries to save some Fibonacci numbers, to use them further and not recalculate, like the simple recursive implementation does @CaptainObvlious Commented Sep 8, 2015 at 18:55
  • @Bathsheba unfortunately, no. it doesn't return the return value of a call immediately, it performs a subsequent addition. Commented Sep 8, 2015 at 19:07

4 Answers 4

2

You probably missed the fact that this function returns two results: One as its return value and one in the "input" parameter passed by reference.

The severe inefficiency of the simple recursive definition of fib is that at each recursive level you must make two different calls to lower levels, even though one of them includes all the work of the other.

By allowing the one that includes all the work of the "other" to also return the result of the "other", you avoid doubling the work at each level.

In the mathematical sense it is no longer a "function" (because of the side effect). But as a function in the programming sense, it sidesteps the efficiency problem of fib by returning two values from one call.

I think it appropriate to mention that in C++, there are more elegant ways to return a pair of values as the result of a function. (Even in C you could return a struct by value).

Edit (in response to your edit):

The main thing I cannot grasp is what does fib(n – 1, prev0) return when we have garbage stored in prev0.

The trick is that prev0 is an output from the function, not an input.

I am a beginner, so, please, be as specific as you can.

The parameter declaration of int & in the function signature lets the function use that parameter as input or output or both as it chooses. This particular function uses it as output.

If you understand the basics of recursive functions, you understand how each level of the recursion has its own copy of the parameter n and of the local variable prev0. But prev1 is not a separate variable. It is effectively an alias for the higher level's prev0. So any read or write of the current level's prev1 really happens to the higher level's prev0.

This level's n is passed "by value" meaning it is a copy of the value of the passed expression (the higher level's n-1 ). But this level's prev1 is passed by reference so it is not a copy of the value of the higher level's prev0, it is an alias of the higher level's prev0.

Sign up to request clarification or add additional context in comments.

3 Comments

Plus one but I think you should mention tail recursion for a full answer.
Thank you so much for your answer, this somehow cleared some things for me (I had no idea that the function also returns the prev1). Could you, please, explain more specifically where does it get value, and get returned. I cannot quite grasp that part.
We have the function declaration int fib(int n, int &prev1) in which the & causes any changes made inside the function to prev1 to happen to whatever variable was passed as prev1. Then we have the first use of prev1 inside the function prev1 = fib(n – 1, prev0); which sets it to the main result of the recursive call. In that same line we see prev0 "passed to" prev1 (but the flow of information is the other way) so the value stored in prev1 of the called function goes into prev0 of this function.
2

Note that prev1 is never read from. Only written to. Let's think about the function this way:

std::pair<int,int> fib_pair(int n) {
    if (n < 2) {
        return std::make_pair(n, 0);
    }

    std::pair<int, int> prev = fib_pair(n-1);
    return std::make_pair(prev.first + prev.second, prev.first);
}

Now it's clearer - there's the one recursive call, and fib(n) returns the two previous numbers, instead of just the one. As a result, we have a linear function instead of an exponential one. We can then rewrite the original version in terms of this one to help us understand both:

int fib(int n, int &prev1) {
    std::pair<int, int> pair = fib_pair(n);
    prev1 = pair.second;
    return pair.first;
}

2 Comments

I expect where you wrote return prev.first + prev.second; you meant return std::make_pair( prev.first + prev.second, prev.first); so you return both fib(n) and fib(n-1), using the fact that the recursive call returned both fib(n-1) and fib(n-2)
@JSF Yes you're right. I'd just copied the whole function and didn't change that line.
2

Let's look at four different functions that compute the nth fibonacci number, using pseudocode instead of restricting the program to a single language. The first follows the standard recursive definition:

function fib(n) # exponential
    if n <= 2 return 1
    return fib(n-1) + fib(n-2)

This function requires exponential time, O(2n), because it recomputes previously-computed fibonacci numbers at each step. The second function requires linear time, O(n), by working from 1 to n instead of n to 1 and keeping track of the two previous fibonacci numbers:

function fib(n) # linear
    if n <= 2 return 1
    prev2 = prev1 = 1
    k := 3
    while k <= n
        fib := prev2 + prev1
        prev2 := prev1
        prev1 := fib
    return fib

That's the same algorithm your program uses, though yours disguises what's going on by operating recursively and passing one of the parameters by a pointer to a variable in an outer scope.

Dijkstra described an algorithm for computing the n fibonacci number in logarithmic time, O(log n), using matrices and the exponentiation by squaring algorithm. I won't give the full explanation here; Dijkstra does it better than I could (though you should beware his convention that F0 = 1 instead of F0 = 0 as we have been doing it). Here's the algorithm:

function fib(n) # logarithmic
    if n <= 2 return 1
    n2 := n // 2 # integer division
    if n % 2 == 1 return square(fib(n2+1)) + square(fib(n2))
    return fib(n2) * (2*fib(n2-1) + fib(n2))

The fourth algorithm operates in constant time, O(1), provided you have floating-point numbers with sufficient precision, using the mathematical definition of the fibonacci numbers:

function fib(n) # constant
    sqrt5 := sqrt(5)
    p := (1 + sqrt5) / 2
    q := 1 / p
    return floor((p**n + q**n) / sqrt5 + 0.5)

For most languages this last algorithm isn't very useful, because for fibonacci numbers of any size you need some kind of unlimited-precision decimal arithmetic library, and although it's constant time it will probably take longer in practice than the simple logarithmic-time algorithm operating on unlimited-precision integers, at least until n is very large.

Comments

0

The obvious (non efficient) implementation of finding a Fibonacci number would be:

int fib(int n) {
    if (n<2) return n;
    return fib(n-2) + fib(n-1);
}

This implementation is inefficient you are doing the same calculations twice.

For example if n is 6, you the algorithm would tell you to add fib(4) and fib(5). To find fib(5), you need to add fib(4) and fib(3). And there you go calculating fib(4) for the second time. As n becomes larger, this will become more inefficient.

The example you provided avoids this inefficiency by remembering the previous fibonacci sequence.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.