想要知道怎么递归转非递归,就得先弄明白递归函数调用和返回的步骤(来源于网课):
调用
- 保存调用信息(参数,返回地址)
- 分配数据区(局部变量)
- 控制转移给被调函数的入口
返回
- 保存返回信息
- 释放数据区
- 控制转移到上级函数
因为递归满足LIFO,所以用栈来实现
递归转非递归方法
最机械的转换方法:
设置t+2个语句标号,t为函数递归调用的次数,令第0个标号为递归入口,1到t标记各个递归规则,t+1标记递归出口;通过入栈出栈来模拟实际递归时系统压栈弹栈的过程。最最机械的部分,则是真的用label在各处做标记,然后用goto语句模拟实现递归过程。
比如,拿求n的阶乘来举例子:
正常递归写法:
int factorial(int n)
{
if (n == 1){
return 1;
} else
return n*factorial(n-1);
}
可以看到其中只有一个递归规则,只有一处递归调用,我们稍微转化一下可以得到:
void factorial(int n, int& f)
{
int u;
if (n == 1){
f = 1;
} else{
factorial(n-1, u);
f = n*u;
}
}
即:
开始设置标号:
- label 0 :第一个可执行语句
- label t+1 :设在函数体结束处
- label i (1<=i<=t): 第i个递归返回处
代码如下:
先创建一个结构体:
struct fac
{
int rd;//标号
int pn;//n
int pf;//f的值
int u;//u的值
};
void factorial(int n, int& f)
{
fac x, tmp;
x.rd = 2;
x.pn = n;
S.push(x);//压入栈底作为监视哨
label0: if ((x = S.top()).pn == 1){//递归入口
S.pop();
x.pf = 1;
S.push(x);
goto label2;
}
//第一处也是本代码唯一一处递归
x.rd = 1;
x.pn = x.pn-1;
S.push(x);
goto label0;
label1: tmp = S.top(); S.pop();
x = S.top(); S.pop();
x.u = tmp.pf;
x.pf = x.pn * x.u;
S.push(x);
//递归出口
label2: x = S.top();
switch (x.rd){
case 0: goto label0; break;
case 1: goto label1; break;
case 2: tmp = S.top();
S.pop();
f = tmp.pf;
break;
}
}
但是会有一些冗余的入栈操作,并且毕竟goto语句现在也不大推荐使用了,比较容易出问题,于是有了优化后的代码:
void factorial(int n, int& f)
{
fac x, tmp;
x.rd = 2;
x.pn = n;
S.push(x);//监视哨
do{
//入栈
while ((x = S.top()).pn > 1){
x.rd = 1;
x.pn = x.pn-1;
S.push(x);
}
x = S.top();
S.pop();
x.pf = 1;
S.push(x);
//开始上升
while ((x = S.top()).rd == 1){
tmp = S.top(); S.pop();
x = S.top(); S.pop();
x.pf = x.pn * tmp.pf;
S.push(x);
}
}while ((x = S.top()).rd != 2);
S.pop();
f = x.pf;
}
有两处递归调用的程序也类似,可以选择从某一个递归语句开始入栈,从另一个上升,大家自己去摸索吧!溜了溜了