5

It's well known all recursive functions can be written using just a single loop and a stack. Although this conversion can be criticized to be ugly or less readable, its main use is obviously to avoid smashing the heap.

There are natural ways to convert simple recursive functions into loops. For instance, take the simple tail-recursion elimination using an accumulator. So far, I have not seen yet a definitive answer for this question.

At least to me, sometimes it seems black magic to convert such recursive functions into loops provided a stack. For instance, think of writing a non-recursive version for

f(n) = n,                    if n <= 1
f(n) = f(n-1) + f(n-2) + 1,  if n > 1

The very point underlying this question is:

  • Is there a lucid, general method to convert a recursive function into a loop + stack?
6
  • Your example doesn't terminate if it were implemented. Commented Dec 30, 2013 at 17:40
  • Oh, I'm sorry. That's the Fibonacci + 1 function. I was meaning f(n-1) instead of f(n+1), I editted. Thank you for the advice! Commented Dec 30, 2013 at 17:42
  • That being said, I'm not sure there is a general strategy as it depends a lot on two things: what information goes down the recursion (via arguments) and what information comes back from the recursion (via return values). It also depends on how many recursive calls there are. So a conversion algorithm may exist but it would be non-trivial. Commented Dec 30, 2013 at 17:44
  • "... all recursive functions can be written ... single loop and stack" may be a bit overstated, but I'm not certain of that. I suppose something like int f(int x, int y) { return f(x/2, y) * f(f(y-3, x)-f(y, x)) / f(y, x*f(x-1, y)/f(x-f(x-1, y), y-1)); } could possibly be rewritten as a loop, but I wouldn't want to have to do it... Then there's mutual recursion situations like f() calling g() which calls f(), and so on, even with larger cycles... Commented Dec 30, 2013 at 17:51
  • 1
    Yes, I'm sure it surely exists. What a compiler does is an example. I imagine one way to solve this problem by simulating exactly what a compiler does, but I think there is a simpler way. Commented Dec 30, 2013 at 17:52

2 Answers 2

5

Feasibility (100%):

From here, we know that any recursive function can be converted to iterate (into a loop) but you need to use a stack yourself to keep the state.


How to do this (generally):

You can check out the article How to replace recursive functions using stack and while-loop to avoid the stack-overflow, which gives examples and steps (10 steps/rules) on how to convert recursive functions to stack and while-loop. See the following part for real example.


Example:

Take the following recursive function as an example:

// Recursive Function "First rule" example
int SomeFunc(int n, int &retIdx)
{
    ...
        if(n>0)
        {
            int test = SomeFunc(n-1, retIdx);
            test--;
            ...
                return test;
        }
        ...
            return 0;
} 

After applying the 10 rules/steps introduced in the article (details shown in comments), you will get:

// Conversion to Iterative Function
int SomeFuncLoop(int n, int &retIdx)
{
    // (First rule)
    struct SnapShotStruct {
        int n;        // - parameter input
        int test;     // - local variable that will be used 
        //     after returning from the function call
        // - retIdx can be ignored since it is a reference.
        int stage;    // - Since there is process needed to be done 
        //     after recursive call. (Sixth rule)
    };
    // (Second rule)
    int retVal = 0;  // initialize with default returning value
    // (Third rule)
    stack<SnapShotStruct> snapshotStack;
    // (Fourth rule)
    SnapShotStruct currentSnapshot;
    currentSnapshot.n= n;          // set the value as parameter value
    currentSnapshot.test=0;        // set the value as default value
    currentSnapshot.stage=0;       // set the value as initial stage
    snapshotStack.push(currentSnapshot);
    // (Fifth rule)
    while(!snapshotStack.empty())
    {
        currentSnapshot=snapshotStack.top();
        snapshotStack.pop();
        // (Sixth rule)
        switch( currentSnapshot.stage)
        {
        case 0:
            // (Seventh rule)
            if( currentSnapshot.n>0 )
            {
                // (Tenth rule)
                currentSnapshot.stage = 1;            // - current snapshot need to process after
                //     returning from the recursive call
                snapshotStack.push(currentSnapshot);  // - this MUST pushed into stack before 
                //     new snapshot!
                // Create a new snapshot for calling itself
                SnapShotStruct newSnapshot;
                newSnapshot.n= currentSnapshot.n-1;   // - give parameter as parameter given
                //     when calling itself
                //     ( SomeFunc(n-1, retIdx) )
                newSnapshot.test=0;                   // - set the value as initial value
                newSnapshot.stage=0;                  // - since it will start from the 
                //     beginning of the function, 
                //     give the initial stage
                snapshotStack.push(newSnapshot);
                continue;
            }
            ...
                // (Eighth rule)
                retVal = 0 ;

            // (Ninth rule)
            continue;
            break; 
        case 1: 
            // (Seventh rule)
            currentSnapshot.test = retVal;
            currentSnapshot.test--;
            ...
                // (Eighth rule)
                retVal = currentSnapshot.test;
            // (Ninth rule)
            continue;
            break;
        }
    }
    // (Second rule)
    return retVal;
} 

BTW, the above article is the Prize winner in Competition <Best C++ article of July 2012> of CodeProject, so it should be trust-able. :)

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

Comments

1

Yes, but the solution won't be much better than the recursive one except maybe reduce the chance for a stack overflow.

By looking at the sequence, which is similar to the Fibonacci sequence except you add 1 to the result for every round except n = (0,1), you see a pattern.

0 1 2 4 7 12 20 
    \  \___ ...\____
    0+1+1  1+2+1   7+12+1

Since the whole generation is dependent on the two previous numbers you can have two variables in a loop representing that and you don't need to have a stack at all.

//intentional generic algol dialect
int modfib (int n)
{
    if( n <= 1 )
      return n;
    n = n - 2;
    int a=2;
    int b=4;
    int tmp;
    while( n-- > 0 )
    {
        tmp=a;
        a=b;
        b = tmp + b +1;
    }
    return a;
}

Until compilers know to do this we still need humans to optimize code.

3 Comments

It's a creative solution but that's exactly what I'm trying to avoid. I tried to harden this recurrence by adding 1 but the point is about what if this f(n) was too hard I couldn't find any pattern, where I have many parameters, local variables, loops, recursive calls and other functions calls? Given a recursive algorithm, is it possible to rewrite it into a non-recursive, equivalent version? Again, thank you for your creative solution.
@CássioJandirPagnoncelli Nop. The whole conversion I did was based on the mathematical sequence and a compiler cannot do this. The rewrite with using a stack + loop is actually recursion and it will stay O(2^n) so it will be just as efficient while the manually rewritten iterative loop is O(n)
Yes, you're right in this sense. In the function I provided, there are O(1.61...^n) function calls whereas in yours it can do in O(n) loops because of the optimization, but this optimization is much closer to ask a compiler to rewrite Fibonacci in dynamic programming than a proper, feasible compiler optimization. Well, the point I was trying to address was not to properly solve that recurrence --actually it has a O(1) solution-- but to convert an ordinary recurrent function into a loop + loop algorithm.

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.