简体   繁体   中英

converting a common recursion to tail recurtion because of stack overflow for large inputs

I have found this algorithm for a program that returns number of possible triangles with a specific perimeter. I know that this problem has some other ways but using them I get time limit error for large numbers maybe because they are not optimized. This one seems much more optimized but I get stack overflow for recursion for large inputs. Can anyone help me optimize it by converting it into tail recursion or do something else with this problem?

Here is my found algorithm:

#include<iostream>
using namespace std;
int foo(int);
int main(){
    int n;
    cin>>n;
    cout<<foo(n);
    return 0;
}
int foo(int n){
    int p=n/2-2;
    int t;
    if(p%6<5&&p%6>0)
        t=(p+6-p%6)/6;
    else if(p%6==5)
        t=(p+6-p%6)/6+1;
    else
        t=p/6;
    if(n==3||n==5||n==6)
        return 1;
    else if(n==4)
        return 0;
    else{
        if(n%2==0)
            return foo(n-3);
        else
            return foo(n+1)+t;
    }
}

Problem is here:

return foo(n+1) + t;
//              ^^^

The addition makes the recursive call to foo not being the very last thing that happens in the function. So you need to move that addition into the function:

return foo(n + 1, t);

To make the other recursive call compatible, you'll just provide addition's neutral element:

return foo(n-3, 0);

New function's signature:

int foo(int n, int offset = 0); // default parameter allows for calling just as before.

Final question remaining: Where to do the addition???

Well, two places are obvious:

if(n==3||n==5||n==6)
    return 1 + offset;
else if(n==4)
    return /*0 +*/ offset;

offset is the accumulated offset of all previous recursive calls! So you will have to add it to whatever you add to next function's recursive call, so:

// ...
else if(n % 2 == 0)
    return foo(n-3, /*0 +*/ offset);
else
    return foo(n+1, t + offset);

Side note: Your algorithm doesn't seem to be suitable for negative numbers. Calling (unmodified) foo with these ends up in recurring until stack overflow (if stack was large enough, would possibly result in undefined behaviour due to signed integer overflow). If you don't intend to use it on negative numbers you absolutely should use unsigned int as data type! Otherwise, it needs appropriate fixing.

The values 0, 1 and 2 need special treatment, too, they lead to negative n (for unmodified foo , see above). But you could do that almost trivially:

if(n == 4)
{ ... }
else if (n < 6)
{ ... }

That would return 1 for n being 0, 1 or 2 (even with tail call optimised version, as offset would be still 0) and even spare some comparisons. If you need to return 0 for the special values: Simple, too:

else if (n < 6)
    return (n >= 3) + offset;

You could even merge all into one single condition:

if(n < 6)
    return (n == 3 || n > 4) + offset; // returning 0 for 0, 1, 2
    return (n <= 3 || n > 4) + offset; // returning 1 for 0, 1, 2

Greatest advantage: during all those recursions, you need only one single check, whereas all others are done only when stop condition is met (ie just once).

And you can move calculation of p and t behind the stop condition(s), you don't use them there anyway...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM