循环和堆栈的递归函数

时间:2013-12-30 17:36:23

标签: loops recursion

众所周知,所有递归函数都可以使用单个循环和堆栈编写。虽然这种转换可能会被批评为丑陋或不太可读,但它的主要用途显然是避免粉碎堆。

有一些自然的方法可以将简单的递归函数转换为循环。例如,使用累加器进行简单的尾递归消除。到目前为止,我还没有看到这个问题的明确答案。

至少对我而言,有时将这种递归函数转换为循环提供堆栈似乎是黑魔法。例如,考虑为

编写非递归版本
f(n) = n,                    if n <= 1
f(n) = f(n-1) + f(n-2) + 1,  if n > 1

这个问题的基础是:

  • 是否有一个清晰的 通用 方法将递归函数转换为循环+堆栈?

2 个答案:

答案 0 :(得分:3)

可行性(100%)

  

here,我们知道任何递归函数都可以转换为迭代(转换为循环),但您需要自己使用堆栈来保持状态。


如何操作(一般):

您可以查看文章How to replace recursive functions using stack and while-loop to avoid the stack-overflow,其中提供了有关如何将递归函数转换为堆栈和while循环的示例和步骤( 10步/规则)。请参阅以下部分以获取真实示例。


示例

以下面的递归函数为例:

// 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;
} 

应用文章中介绍的10条规则/步骤(评论中显示的详细信息)后,您将获得:

// 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;
} 

顺便说一下,上面的文章是CodeProject的Prize winner in Competition <Best C++ article of July 2012>,因此它应该是可信任的。 :)

答案 1 :(得分:0)

是的,但解决方案不会比递归解决方案好很多,除非可能减少堆栈溢出的可能性。

通过查看类似于Fibonacci序列的序列,除了你为除n =(0,1)之外的每一轮的结果加1,你会看到一个模式。

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

由于整个生成依赖于前两个数字,因此在循环中可以有两个变量来代表它,并且根本不需要堆栈。

//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;
}

在编译器知道这样做之前,我们仍然需要人来优化代码。