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?
Cássio
  • 649
  • 1
  • 7
  • 17
  • Your example doesn't terminate if it were implemented. – Simeon Visser Dec 30 '13 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! – Cássio Dec 30 '13 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. – Simeon Visser Dec 30 '13 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... – twalberg Dec 30 '13 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. – Cássio Dec 30 '13 at 17:52
  • @twalberg: It's not beautiful but it's useful when stack frame overhead is of concern. Actually, all functions can be simulated using a loop and stack, that's exactly what compilers do: they build the stack frame (or "activation record"), calls the function, and the return is put in a known place (RAM or register). – Cássio Dec 30 '13 at 18:00

2 Answers2

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. :)

Community
  • 1
  • 1
herohuyongtao
  • 49,413
  • 29
  • 133
  • 174
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.

Sylwester
  • 47,942
  • 4
  • 47
  • 79
  • 1
    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ássio Dec 30 '13 at 19:11
  • @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) – Sylwester Dec 30 '13 at 19:17
  • 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. – Cássio Dec 30 '13 at 19:36