【algorithm】尾递归

尾递归和一般的递归不同在对内存的占用,普通递归创建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可以,我不知道为什么),这里是用它做个例子。====================================

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

【algorithm】尾递归

时间: 2024-11-12 06:18:15

【algorithm】尾递归的相关文章

浅谈尾递归

浅谈尾递归 2013-02-10 14:12:57 在<数据结构与算法分析:C描述>(Data Structures and Algorithm Analysis In C)的第三章中,以打印链表为例,提到了尾递归(tail recursion)并指出了尾递归是使用递归极其不当的例子,它指出虽然编译器会对尾递归自动优化,但即便如此最好还是不要去写尾递归.而我在<算法精解:C语言描述>(Mastering Algorithms with C)中也看到书中提到编译器会对尾递归进行优化,

尾递归

通过阶乘计算来认识尾递归.阶乘可以用下面的表达式来描述: n!=n*(n-1)*(n-2)…3*2*1 根据上面的表达式我们可以概括出下面的算法来计算阶乘: n!=n*(n-1)! public int Factorial(int number) { if (number == 1) { return 1; } var temp = number * Factorial(number - 1); return temp; } 函数调用: var calculator=new Calculator

PLA Percentron Learning Algorithm #台大 Machine learning #

Percentron Learning Algorithm 于垃圾邮件的鉴别 这里肯定会预先给定一个关于垃圾邮件词汇的集合(keyword set),然后根据四组不通过的输入样本里面垃圾词汇出现的频率来鉴别是否是垃圾邮件.系统输出+1判定为垃圾邮件,否则不是.这里答案是第二组. 拿二维数据来做例子.我们要选取一条线来划分红色的叉叉,和蓝色的圈圈样本点(线性划分).怎么做呢?这里的困难之处就在于,其实可行的解可能存在无数条直线可以划分这些样本点.很难全部求解,或许实际生活中并不需要全部求解.于是,

STL algorithm算法is_partitioned(26)

is_partitioned原型: std::is_partitioned template <class InputIterator, class UnaryPredicate> bool is_partitioned (InputIterator first, InputIterator last, UnaryPredicate pred); 测试范围内的元素是否是以pred为准则的一个划分.如果是,则返回true,否则返回false. 划分的意思是说,对每个元素进行pred(*it),得

支付宝支付php的demo或sdk报错 Warning: openssl_sign() [function.openssl-sign]: Unknown signature algorithm. in

最近在做支付宝支付,在本地测试一切正常,上传到服务器就遇到报错: Warning: openssl_sign() [function.openssl-sign]: Unknown signature algorithm. in 后来查了查,是我的服务器上PHP环境支持openssl_sign()但却不支持 OPENSSL_ALGO_SHA256这样的参数,问了一下大佬,才发现这个参数是在php5.4.8以上版本才支持,低版本的是使用的SHA256,于是乎试了一下,搞定! 报错原因是支付宝的dem

Berlekamp-Massey Algorithm [for Team Problem 5525]

Input: 第一行为两个正整数n,m 第二行为n个整数a1..an 最后一行为一个正整数k Output: 为一个整数,代表方案数对1000000007取模的值 Sample Input 5 3 1 1 2 0 2 2 Sample Output 3 来自毛爷爷17年论文 Berlekamp-Massey Algorithm直接开算 1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const

Strassen algorithm(O(n^lg7))

Let A, B be two square matrices over a ring R. We want to calculate the matrix product C as {\displaystyle \mathbf {C} =\mathbf {A} \mathbf {B} \qquad \mathbf {A} ,\mathbf {B} ,\mathbf {C} \in R^{2^{n}\times 2^{n}}} If the matrices A, B are not of ty

尾递归和线性递归

1.递归的定义 函数直接或间接的调用自己 使用递归时,必须有明确的结束递归的条件 2.递归的适用场合 数据的定义按照递归定义(比如求n!) 问题的解法适用于使用递归 数据的结构是按递归定义的(比如二叉树) 3.线性递归 也就是普通递归,下一次递归数据的计算要依赖于上一次递归的结果和参数,当数据量较小时执行效率与尾递归几乎没区别,但当数据量较大,迭代次数较多时,由于每次递推都要在内存中开辟一个栈空间,用来存储上次递推的结果和参数,这样的算法将导致严重的内存开销,甚至造成内存溢出,抛出java.la

尾递归 - 以斐波那契数列为例说明

尾递归 前言:今天上网看帖子的时候,看到关于尾递归的应用(http://bbs.csdn.net/topics/390215312),大脑中感觉这个词好像在哪里见过,但是又想不起来具体是怎么回事.如是乎,在网上搜了一下,顿时豁然开朗,知道尾递归是怎么回事了.下面就递归与尾递归进行总结,以方便日后在工作中使用. 1.递归 关于递归的概念,我们都不陌生.简单的来说递归就是一个函数直接或间接地调用自身,是为直接或间接递归.一般来说,递归需要有边界条件.递归前进段和递归返回段.当边界条件不满足时,递归前