Loops 循环和堆栈的递归函数

Loops 循环和堆栈的递归函数,loops,recursion,Loops,Recursion,众所周知,所有递归函数都可以只用一个循环和一个堆栈来编写。尽管这种转换可能会被批评为丑陋或可读性较差,但它的主要用途显然是避免破坏堆 有一些自然的方法可以将简单的递归函数转换为循环。例如,使用累加器消除简单的尾部递归。到目前为止,我还没有看到这个问题的明确答案 至少对我来说,有时将此类递归函数转换为堆栈提供的循环似乎是一种魔术。例如,考虑为其编写一个非递归版本 f(n) = n, if n <= 1 f(n) = f(n-1) + f(n-2) + 1

众所周知,所有递归函数都可以只用一个循环和一个堆栈来编写。尽管这种转换可能会被批评为丑陋或可读性较差,但它的主要用途显然是避免破坏堆

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

至少对我来说,有时将此类递归函数转换为堆栈提供的循环似乎是一种魔术。例如,考虑为其编写一个非递归版本

f(n) = n,                    if n <= 1
f(n) = f(n-1) + f(n-2) + 1,  if n > 1
f(n)=n,如果n1
这个问题的基本要点是:

  • 是否有一种清晰的、通用的方法将递归函数转换为循环+堆栈
  • 可行性(100%)

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


如何执行此操作(通常):

您可以查看这篇文章,它提供了有关如何将递归函数转换为堆栈和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;
} 
//转换为迭代函数
int SomeFuncLoop(int n,int&retIdx)
{
//(第一条规则)
结构快照结构{
int n;//-参数输入
int test;//-将使用的局部变量
//从函数调用返回后
//-retIdx可以忽略,因为它是一个引用。
int stage;//-因为需要执行流程
//递归调用之后。(第六条规则)
};
//(第二条规则)
int retVal=0;//使用默认返回值初始化
//(第三条规则)
堆栈快照堆栈;
//(第四条规则)
SnapShotStruct当前快照;
currentSnapshot.n=n;//将该值设置为参数值
currentSnapshot.test=0;//将该值设置为默认值
currentSnapshot.stage=0;//将该值设置为初始阶段
snapshotStack.push(当前快照);
//(第五条规则)
而(!snapshotStack.empty())
{
currentSnapshot=snapshotStack.top();
snapshotStack.pop();
//(第六条规则)
开关(currentSnapshot.stage)
{
案例0:
//(第七条规则)
如果(currentSnapshot.n>0)
{
//(第十条规则)
currentSnapshot.stage=1;//-当前快照需要在
//从递归调用返回
snapshotStack.push(currentSnapshot);//-必须在
//新快照!
//创建用于调用自身的新快照
SnapShotStruct新闻快照;
newSnapshot.n=currentSnapshot.n-1;//-将参数作为给定参数
//当呼唤自己的时候
//(SomeFunc(n-1,retIdx))
newSnapshot.test=0;//-将该值设置为初始值
newSnapshot.stage=0;//-因为它将从
//功能的开始,
//开始阶段
快照堆栈。推送(新闻快照);
继续;
}
...
//(第八条规则)
retVal=0;
//(第九条规则)
继续;
打破
案例1:
//(第七条规则)
currentSnapshot.test=retVal;
currentSnapshot.test--;
...
//(第八条规则)
retVal=currentSnapshot.test;
//(第九条规则)
继续;
打破
}
}
//(第二条规则)
返回返回;
} 


顺便说一句,以上文章是CodeProject的比赛中的获奖者,因此应该是可信的:

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

通过查看序列,它类似于斐波那契序列,只是除了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;
}
//有意通用algol方言
int modfib(int n)
{
if(n0)
{
tmp=a;
a=b;
b=tmp+b+1;
}
返回a;
}

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

如果实现了您的示例,它不会终止。哦,对不起。这就是斐波那契+1函数。我是指f(n-1)而不是f(n+1),我编辑。谢谢你的建议!尽管如此,我不确定是否有一个通用的策略,因为它在很大程度上取决于两件事:什么信息进入递归(通过参数)和什么信息从递归(通过返回值)返回。它还取决于有多少递归调用。因此,一个转换算法可能存在,但它将是不平凡的。”…所有递归函数都可以编写…单循环和堆栈”可能有点言过其实,但我不确定这一点。我假设像
intf(intx,inty){返回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));}
可能被重写为一个循环,但我不想这么做。。。然后是相互递归的情况,比如