递归、尾递归和迭代(转)

作者:匿名用户
链接:https://www.zhihu.com/question/20761771/answer/19996299
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

尾递归是什么?

尾递归和一般的递归不同在对内存的占用,普通递归创建stack累积而后计算收缩,尾递归只会占用恒量的内存(和迭代一样)。SICP中描述了一个内存占用曲线,用以上答案中的Python代码为例(普通递归):

def recsum(x):
  if x == 1:
    return x
  else:
    return x + recsum(x - 1)

当调用recsum(5),Python调试器中发生如下状况:

recsum(5)
5 + recsum(4)
5 + (4 + recsum(3))
5 + (4 + (3 + recsum(2)))
5 + (4 + (3 + (2 + recsum(1))))
5 + (4 + (3 + (2 + 1)))
5 + (4 + (3 + 3))
5 + (4 + 6)
5 + 10
15

这个曲线就代表内存占用大小的峰值,从左到右,达到顶峰,再从右到左收缩。而我们通常不希望这样的事情发生,所以使用迭代,只占据常量stack space(更新这个栈!而非扩展他)。
---------------------
(一个替代方案:迭代

for i in range(6):
  sum += i

因为Python,Java,Pascal等等无法在语言中实现尾递归优化(Tail Call Optimization, TCO),所以采用了for, while, goto等特殊结构代替recursive的表述。Scheme则不需要这样曲折地表达,一旦写成尾递归形式,就可以进行尾递归优化。
---------------------
Python中可以写(尾递归):

def tailrecsum(x, running_total=0):
  if x == 0:
    return running_total
  else:
    return tailrecsum(x - 1, running_total + x)

理论上类似上面:

tailrecsum(5, 0)
tailrecsum(4, 5)
tailrecsum(3, 9)
tailrecsum(2, 12)
tailrecsum(1, 14)
tailrecsum(0, 15)
15

观察到,tailrecsum(x, y)中形式变量y的实际变量值是不断更新的,对比普通递归就很清楚,后者每个recsum()调用中y值不变,仅在层级上加深。所以,尾递归是把变化的参数传递给递归函数的变量了
怎么写尾递归?形式上只要最后一个return语句是单纯函数就可以。如:

return tailrec(x+1);

return tailrec(x+1) + x;

则不可以。因为无法更新tailrec()函数内的实际变量,只是新建一个栈。

但Python不能尾递归优化(Java不行,C可以,我不知道为什么),这里是用它做个例子。
====================================

如何优化尾递归
在编译器处理过程中生成中间代码(通常是三地址代码),用编译器优化。

时间: 2024-10-29 19:10:37

递归、尾递归和迭代(转)的相关文章

逆转序列的递归/尾递归(+destructuring assignment)实现(JavaScript + ES6)

这里是用 JavaScript 做的逆转序列(数组/字符串)的递归/尾递归实现.另外还尝鲜用了一下 ES6 的destructuring assignment + spread operator 做了一个更 functional 的版本(只支持数组). 正确性能通过测试(参见 放在我 Github 上的 demo,顺手写了一个小小的测试框架),不过效率就要打问号了——特别是用了 ES6 特性的版本.这里主要是写来玩 JS 的函数式特性的. 1. 逆转序列的递归实现 先用 Haskell 实现做草

程序算法艺术与实践:递归策略之递归,循环与迭代

众所周知,递归的实现是通过调用函数本身,函数调用的时候,每次调用时要做地址保存,参数传递等,这是通过一个递归工作栈实现的,同时影响效率的.递归是利用系统的堆栈保存函数当中的局部变量来解决问题的,而递归就是在栈处理栈上一堆的指针指向内存中的对象,这些对象一直不被释放,直到递归执行到最后一次后,才释放空间. 循环效率与递归效率 递归与循环是两种不同的解决问题的典型思路.当然也并不是说循环效率就一定比递归高,递归和循环是两码事,递归带有栈操作,循环则不一定,两个概念不是一个层次,不同场景做不同的尝试.

[LeetCode系列]爬梯问题的递归解法转换为迭代解法

有一个n阶的梯子, 你每次只能爬1阶或2阶, 请问共有多少种登顶的爬法?(正好爬完n阶, 不能多也不能少) 本题最优解是直接套用菲波那切数列即可(因为菲波那切数列的第n个元素正好等于第n-1个元素和第n-2个元素的和, 与本题的要求完全相同). 递归解法: 1 int climbStairs(int n) { 2 if (n < 3) return n; 3 return climbStairs(n-1) + climbStairs(n-2); 4 } 思路很清晰, 递归到当前阶数小于3阶时返回

单向链表反转算法——递归版和迭代版

最近在做笔试题时,遇到一道编程题:单向链表反转算法.一时紧张,没写出来就提前交卷了,然而交完卷就想出来了... 最初想出来的是递归版,遗憾的是没能做到尾递归,后来又琢磨出了迭代版.后来用实际编译运行测试了一遍,能正常运行. 递归版的灵感来源于<Haskell 趣学指南>中非常简洁的快速排序算法的实现,其思想是将单向链表分割头部和尾部.其中头部指是链表的第一个节点,尾部是指除去第一个节点后的子链表.通过递归的方法,将子链表继续分割成头部和尾部,直至尾部指剩下一个节点,无法继续分割,然后将头部和尾

斐波那契数列 递归 尾递归 递推 C++实现

==================================声明================================== 本文原创,转载请注明作者和出处,并保证文章的完整性(包括本声明). 本文不定期修改完善,为保证内容正确,建议移步原文处阅读. 本文链接:http://www.cnblogs.com/wlsandwho/p/4205524.html ===============================================================

迭代、递归替代循环

循环(迭代)与递归的区别 1. 递归算法与迭代算法的设计思路区别在于:函数或算法是否具备收敛性,当且仅当一个算法存在预期的收敛效果时,采用递归算法才是可行的,否则,就不能使用递归算法. 当然,从理论上说,所有的递归函数都可以转换为迭代函数,反之亦然,然而代价通常都是比较高的. 但从算法结构来说,递归声明的结构并不总能够转换为迭代结构,原因在于结构的引申本身属于递归的概念,用迭代的方法在设计初期根本无法实现,这就像动多态的东西并不总是可以用静多态的方法实现一样.这也是为什么在结构设计时,通常采用递

学习了解递归和尾递归的区别

学习了递归和尾递归后,自己做了一些简单的总结,便于学习. 以典型的斐波那契数列为例,比较二者: import java.util.Scanner; public class testFibonacci { public static void main(String[] args){ Scanner sc = new Scanner(System.in); int n = sc.nextInt(); System.out.println(fibonacci1(n)); System.out.pr

漫谈递归和迭代

递归(recursion)在计算机科学中是指一种通过重复将问题分解为同类问题的子问题而解决问题的方法.可以极大地减少代码量.递归的能力在于用有限的语句来定义对象的无限集合.递归式方法可以被用于解决很多计算机科学问题,因此它是计算机科学中十分重要的一个概念.绝大多数编程语言支持函数的自调用,在这些语言中函数可以通过调用自身来进行递归.计算理论可以证明递归可以完全取代循环,因此在很多函数编程语言中习惯用递归来实现循环. 与重复密切相关的是递归,在递归技术中,概念是直接或间接由其自身定义的.例如,我们

递归与迭代_1 2016.4.21

迭代乃人工,递归方神通 To interate is human,to recurse,divine 一.定义 (1) 迭代 是重复反馈过程的活动,其目的通常是为了逼近所需目标或结果 每一次对过程的重复称为一次"迭代",而每一次迭代得到的结果会作为下一次迭代的初始值 (2) ① 程序调用自身的编程技巧称为递归( recursion) 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量