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));}
可能被重写为一个循环,但我不想这么做。。。然后是相互递归的情况,比如