0
int test(int n) {
    if(n <= 2)
    {
         return n;
    }
    return test(n-1) + test(n-2) + test(n-3);
}

Is there any way to speed it up without changing function declaration, when n becomes larger, it will take plenty of time to get the out put.

0.1.2.3.6.11.20

when n = 3, it should get the out put 0+1+2=3

when n = 5, it should get the out put 2+3+6=11

JamesTuT
  • 5
  • 2
  • 4
    Use memoization so you don't do the full recursion for inputs you've already processed. – Barmar Oct 06 '21 at 21:23
  • Is using recursion required? Because this is basically a modified Fibonacci calculator, and Fibonacci is actually a terrible problem for recursion (it's described well with recursion, but it's `O(2**n)` implemented recursively, vs. `O(n)` implemented iteratively; this code would be `O(3**n)` recursive, still `O(n)` iterative), unless the *intent* is to force you to learn to use memoization to optimize recursion. – ShadowRanger Oct 06 '21 at 21:31
  • What does “without changing test(int n)` mean? Do you only not want to change how the function is declared, `test(int n)`, or do you not want to change the function at all? If you do not want to change the function at all, how do you imagine a function’s behavior (including performance) can be changed without changing the function’s source code? Change compiler optimization? Change how it is called? Use macros to sneakily change the effective source code? – Eric Postpischil Oct 06 '21 at 21:35
  • `Is there any way to speed it up without changing test(int n)` The only idea I have is to buy faster computer – 0___________ Oct 06 '21 at 21:44
  • There surely is a `O(1)` algorithm for calculating this, just like the one for Fibonacci numbers. But I don't know how to make one. – anatolyg Oct 06 '21 at 21:49
  • I know how to deal with it by using loop, but at this time recursion is required. – JamesTuT Oct 06 '21 at 21:49
  • @0___________: A computer core that's 1000x faster (roughly equivalent to jumping from a 1980 computer to a 2010-era computer core) would be able to handle an input roughly 6-7 numbers higher (so say, computing up to `test(36)` in the same time the old computer took to do `test(30)`. Not exactly scalable if you need a time machine to travel a century into the future to compute `test(58)`. :-) – ShadowRanger Oct 06 '21 at 21:50
  • @JamesTuT: Yeah, if recursion is required, then you have to learn how to do memoization in C (hint, it's a pain). Given it's almost certainly the intent of your homework, you should probably *try* to do it yourself before asking others for help. – ShadowRanger Oct 06 '21 at 21:50
  • @ShadowRanger but he does not want to change the code – 0___________ Oct 06 '21 at 21:51
  • @0___________: The OP means they can't change the prototype (they edited to clarify they mean "function declaration", but even before, they only said `test(int n)` couldn't change); memoization can be implemented without changing the prototype. – ShadowRanger Oct 06 '21 at 21:58
  • @ShadowRanger I have figure it out, thank you. – JamesTuT Oct 06 '21 at 22:09
  • I recently answered a similar (ignoring the modulo arithmetic) [question](https://stackoverflow.com/questions/69134656/reducing-the-time-complexity-of-recursive-fibonacci-like-function-in-c/69137038#69137038). The *other* [answer](https://stackoverflow.com/a/69137038/4944425) by [Jérôme Richard](https://stackoverflow.com/users/12939557/j%c3%a9r%c3%b4me-richard) shows a more efficient approach and gives hints about the mathematical background. – Bob__ Oct 06 '21 at 22:26
  • @anatolyg: I remember in the DOS era somebody wanted an O(1) factorial. I was happy to provide. It was also smaller than the O(N) factorial. But for fibbonachi it won't be. – Joshua Oct 06 '21 at 22:28
  • @JamesTuT: If you figured it out, I'd encourage you to post your own answer. SO benefits from self-answered posts too! – ShadowRanger Oct 06 '21 at 22:33

4 Answers4

1

Imagine you want to find test(10). As a human, you will naturally find the algorithm to find that. Start making a table of results.

n = 0 -- result is 0
n = 1 -- result is 1
n = 2 -- result is 2

To continue the table, just use the last few numbers in the table:

n = 0 -- result is 0
n = 1 -- result is 1
n = 2 -- result is 2
n = 3 -- result is 3
n = 4 -- result is 6
...

The algorithm is "take last 3 numbers and add them, then write the answer in the new line in your table". This is easy to implement. Something like this:

n1 = 2
n2 = 1
n3 = 0
for i from 3 to n
    // Imagine you have everything up to i written on paper.
    // Here n1 is the last number, n2 is before that, n3 is before that.
    // Try to continue writing your table.

    result = n1 + n2 + n3

    // Now imagine you have written the result on paper.
    // What are the 3 last numbers now?

    n3 = n2
    n2 = n1
    n1 = result

    // Verify that in a debugger for better visualization.

// Now, after the loop is finished, n1 (the last number) is the correct answer.

return n1
anatolyg
  • 26,506
  • 9
  • 60
  • 134
  • Thank you, I know how to deal with it by using loop, but the restrict is that only use recursion to deal with it. – JamesTuT Oct 06 '21 at 21:48
0

If you want to keep using recursion you have to use memoization, or caching. The idea is, when calculating a result, store it for later.

I suggest having an array of sufficient size, say 100 entries (seeing that the numbers grow just a bit slower than 2n, this should be enough to represent any 64-bit number). Initialize it to 0:

int test(int n)
{
    static int cache[100] = {0};
    ...
}

After calculating a result, store it in the correct index:

int test(int n)
{
    ...
    result = ...
    cache[n] = result;
    return result;
}

But before applying the formula, check if the cache contains the result:

int test(int n)
{
    ...
    if (cache[n] != 0)
        result = cache[n];
    ...
}
anatolyg
  • 26,506
  • 9
  • 60
  • 134
  • 1
    Presumably you'd initialize it with `static int cache[100] = {0, 1, 2};` to simplify matters (no need to check for `n <= 2` at all; if it's positive, it's in the cache, or you can compute it from the cache). – ShadowRanger Oct 06 '21 at 22:35
  • @ShadowRanger: Not checking for `n <= 2` causes a problem because the 0th term of the series is zero, which is also used to indicate a cached value has not been set. – Eric Postpischil Oct 07 '21 at 01:05
  • @EricPostpischil: True. I'd suggest -1 as the marker, but initializing a large static array to anything but `0`s is a pain in the butt. – ShadowRanger Oct 07 '21 at 01:42
0

Time to get out the tail recursion hammer

static int testk(int i, int n, int a, int b, int c)
{
   if (i == n) return a + b + c;
   return testk(i + 1, n, a + b + c, a, b);
}

int test(int n) {
    if (n < 2) return n;
    return testk(3, n, 2, 1, 0);
}

This is a direct translation of anatolyg's answer to tail recursion.

Joshua
  • 40,822
  • 8
  • 72
  • 132
0

As you probably figured out, the reason your recursive solution is slow is because you recompute answers that had already been computed before.

This is why memoization works, and it allows you to maintain your top down algorithmic translation of the sequence as mathematically defined.

The disadvantage of complete memoization is that the table can be arbitrarily large. If O(1) time (at the cost of O(n) space) is not required, and O(n) time is sufficient, it is possible to perform the computation with O(1) space, and this has been illustrated in other answers as well.

All that is really required is that for any recursive calculation of test(n-1), the values for test(n-2) and test(n-3) should also be available. If you do not like creating a helper recursive function to track this information, then the below is an alternative that uses a single recursive function.

int test(int n) {

    typedef struct { int x[2]; } state;
    static const state init = { 0, 1 };
    static state last;

    if (n > 0) {
        last = init;
        return test(-n);
    }

    n = -n;
    if (n < 3) return n;

    // After the below call, last has test(n-2) and test(n-3)
    int x = test(-(n-1));
    int result = x + last.x[0] + last.x[1];
    last.x[0] = last.x[1];
    last.x[1] = x;
    return result;
}

Try it online!

jxh
  • 69,070
  • 8
  • 110
  • 193