将递归转化成迭代的通用技术

阅读更多关于《将递归转化成迭代的通用技术》

从理论上讲,只要允许使用栈,所有的递归程序都可以转化成迭代。

但是并非所有递归都必须用栈,不用堆栈也可以转化成迭代的,大致有两类

  1. 尾递归:可以通过简单的变换,让递归作为最后一条语句,并且仅此一个递归调用。

 

  1. 自顶向下->自底向上:对程序的结构有深刻理解后,自底向上计算,比如 fibnacci 数列的递归->迭代转化。

 

 

对于非尾递归,就必须使用堆栈。可以简单生硬地使用堆栈进行转化:把函数调用和返回的地方翻译成汇编代码,然后把对硬件 stack 的  push, pop 操作转化成对私有 stack 的 push, pop ,这其中需要特别注意的是对返回地址的 push/pop,对应的硬件指令一般是 call/ret。使用私有 stack 有两个好处:

  1. 可以省去公用局部变量,也就是在任何一次递归调用中都完全相同的函数参数,再加上从这些参数计算出来的局部变量。
  2. 如果需要得到当前的递归深度,可以从私有 stack 直接拿到,而用递归一般需要一个单独的 depth 变量,然后每次递归调用加 1。

我们把私有 stack 元素称为 Frame,那么 Frame 中必须包含以下信息:

  1. 返回地址(对应于每个递归调用的下一条语句的地址)
  2. 对每次递归调用都不同的参数

通过实际操作,我发现,有一类递归的 Frame 可以省去返回地址!所以,这里又分为两种情况:

  • Frame 中可以省去返回地址的递归:仅有两个递归调用,并且其中有一个是尾递归。

 

  • Frame 中必须包含返回地址的递归,这个比较复杂,所以我写了个完整的示例:
    • 以MergeSort为例,因为 MergeSort 是个后序过程,两个递归调用中没有任何一个是尾递归
    • MergeSort3 使用了 GCC 的 Label As Value 特性,只能在 GCC 兼容的编译器中使用
    • 单纯对于这个实例来说,返回地址其实只有两种,返回地址为 0 的情况可以通过判断私有栈(varname=stk)是否为空,stk为空时等效于 retaddr == 0。如果要精益求精,一般情况下指针的最低位总是0,可以把这个标志保存在指针的最低位,当然,如此的话就无法对 sizeof(T)==1 的对象如 char 进行排序了。

    •