将递归函数转化为非递归再讨论
突然发现很早以前就写过关于递归转化为非递归函数的两篇博文,当时都是自以为懂,认为我会转化,不就是一个栈吗,大致原理我都知道,只不过具体算法写起来耗费一点时间而已。是的。这样说没什么问题。
但是我很喜欢cs:app(ics)即深入理解计算机系统课本上一句话,不要欺骗自己说我大致懂原理,不必纠结于具体细节不必过多练习了。比如这个问题,以前自以为自己懂,但是写得不熟练,其实是理解不够深刻。学了半学期ics,写了看了很多汇编代码调用递归函数以后,我对递归转化为非递归理解地更深刻了。甚至理论上可以写出汇编风格的C语言代码,转化递归非递归自然可以了。下面我们结合汇编实现方法,基于练习了大量汇编代码的基础上,再来看看这个细节并不简单的问题。
下面来看看数算课本里对机械做法的介绍
1. 设置一工作栈保存当前工作记录
2. 设置 t+2个语句标号
3. 增加非递归入口
4. 替换第 i (i = 1, …, t)个递归规则
5. 所有递归出口处增加语句:goto label t+1;
6. 标号为t+1的语句的格式
7. 改写循环和嵌套中的递归
8. 优化处理
只完成机械转化,不考虑7.8.,下面我们讨论两个问题:
1.需要在工作状态里保存些什么?
类比汇编代码写法我们知道,要保存自变量,返回地址,不需要保存结果,只需要一个全局变量保存结果即可。
2.t到底是什么?
课本里面说t是递归规则数目,个人觉得不是很清楚(自己搞懂以后才明白课本在说什么),不如说是递归内部return address种类更好一点,也就是说t就是递归调用自身方式数目。
讨论清楚这些问题,可以看看有些汇编风格的非递归代码。
#include<stdio.h>
struct info_type
{
int m,n;
int ra;//return address;
info_type(){};
info_type(int _m,int _n,int _ra):m(_m),n(_n),ra(_ra){};
};
const int SIZE=10000;
info_type stack[SIZE];
int sp=-1;//stack_pointer
void push(info_type val)
{
stack[++sp]=val;
return;
}
info_type top()
{
return stack[sp];
}
void pop()
{
--sp;
}
int ack(int m,int n)
{
info_type info;
int ans;
push(info_type(m,n,0));
goto Lcall;
Lcall:
info=top();
if (info.m==0)
{
ans=info.n+1;
goto Lret;
}
if (info.n==0)
{
push(info_type(info.m-1,1,1));
goto Lcall;
}
push(info_type(info.m,info.n-1,2));
goto Lcall;
L1:
goto Lret;
L2:
info=top();
push(info_type(info.m-1,ans,3));
goto Lcall;
L3:
goto Lret;
Lret:
info=top();
pop();
if (info.ra==0)
return ans;
if (info.ra==1)
goto L1;
if (info.ra==2)
goto L2;
if (info.ra==3)
goto L3;
}
int ack_test(int m,int n)
{
if (m==0)
return n+1;
else if (n==0)
return ack_test(m-1,1);
else
return ack_test(m-1,ack_test(m,n-1));
}
int main()
{
bool test=true;
for (int i=0;i<4;i++)
for (int j=0;j<4;j++)
if (ack(i,j)!=ack_test(i,j))
{
printf("Wrong Answer! ack(%d,%d)=%4d , instead of %4d\n",
i,j,ack_test(i,j),ack(i,j));
test=false;
}
if (test)
printf("Accepted!\n");
return 0;
}