所有递归都可以变循环

阅读: 评论:0

所有递归都可以变循环

所有递归都可以变循环

所有递归都可以变循环

  这是函数帧的应用之二。

  还记得大一的C程序设计课上讲到汉诺塔的时候老师说: 所有递归都可以用循环实现。这听起来好像可行,然后我就开始想怎么用循环来解决汉诺塔问题,我大概想了一个星期,最后终于选择了……放弃…… 当然,我不是来推翻标题的,随着学习的深入,以及"自觉修炼",现在我可以肯定地告诉大家:所有递归都可以用循环实现,更确切地说:所有递归都可以用循环+栈实现 (就多个数据结构,还不算违规吧O(∩_∩)O~)。

  通过在我们自定义的栈中自建函数帧,我们可以达到和函数调用一样的效果。但是因为这样做还是比较麻烦,所以就不转换汉诺塔问题了,而是转换之前的那个递归求解阶乘的程序(fac.c):

#include <stdio.h>int fac(int n)
{if(n <= 1)return 1;return n * fac(n-1);
}int main()
{int n = 3;int ans = fac(n);printf("%d! = %dn", n, ans);return 0;
}

技术难点

  我们可以在自建的函数帧中存储局部变量、存储参数, 但是我们不能存返回地址,因为我们得不到机器指令的地址! 不过,C语言有一个类似于指令地址的东西:switch case 中的 case子句,我们可以用一个case代表一个地址,技术难点就此突破了。

源程序

  虽然我简化了很多步骤,但源程序还是比较长(fac2.c):

#include <stdio.h>// 栈的设置
#define STACKDEEPTH 1024
int stack[STACKDEEPTH];
int *esp = &stack[STACKDEEPTH];
#define PUSH(a) *(--esp) = a
#define POP(b)  b = *(esp++)// 其它模拟寄存器
int eax;// 存返回值
int eip;// 用于分支选择int main()
{int n = 3;// 模仿 main 调用 fac(n)PUSH(n);PUSH(10002);// 模仿返回 main 的地址eip = 10000;do{switch(eip){case 10000:--esp;// 为帧分配空间if(esp[2] <= 1){// 模仿递归终止条件eax = 1;++esp;// 回收帧空间POP(eip);}else{// 模仿递归计算 fac(n-1)esp[0] = esp[2] - 1;PUSH(10001);eip = 10000;}break;case 10001:// 返回 n * (fac(n-1)的结果)eax = esp[2] * eax;++esp;// 回收帧空间POP(eip);break;}}while(eip != 10002);printf("%d! = %dn", n, eax);return 0;
}

自建的函数帧

  为了简化程序,ebp我们就不用了,完全用esp来操作栈,一个函数帧只占用 8 个字节:

  在计算到 fac(1) 的时候,栈中内容如下:

  比起肆意挥霍栈空间的 gcc(fac帧用了32字节,浪费了20字节,实际使用了12字节),我们的程序真的是太节省了(一帧只用8字节)。

小结

  当然,本文的方法只用于学术讨论,说明所有递归都可以变循环,编程的时候还是不要这么用。因为代码复杂、容易出错、难以理解,唯一的优点是能省空间省到极限。

  这种递归变循环的方式并没有降低时间复杂度,但却是通用的(所有递归都可以这么变循环);而有一部分递归可以基于巧妙的算法变成循环,并且大大降低时间复杂度,如:动态规划、贪心算法(详见《算法导论》)。

本文发布于:2024-02-01 08:43:55,感谢您对本站的认可!

本文链接:https://www.4u4v.net/it/170674823535359.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:递归
留言与评论(共有 0 条评论)
   
验证码:

Copyright ©2019-2022 Comsenz Inc.Powered by ©

网站地图1 网站地图2 网站地图3 网站地图4 网站地图5 网站地图6 网站地图7 网站地图8 网站地图9 网站地图10 网站地图11 网站地图12 网站地图13 网站地图14 网站地图15 网站地图16 网站地图17 网站地图18 网站地图19 网站地图20 网站地图21 网站地图22/a> 网站地图23