尾递归

尾调用和尾递归

感情迁移 提交于 2019-11-28 01:56:48
尾调用 1. 定义 尾调用是函数式编程中一个很重要的概念,当一个函数执行时的最后一个步骤是返回另一个函数的调用,这就叫做尾调用。 注意这里函数的调用方式是无所谓的,以下方式均可: 函数调用: func(···) 方法调用: obj.method(···) call调用: func.call(···) apply调用: func.apply(···) 复制代码 并且只有下列表达式会包含尾调用: 条件操作符: ? : 逻辑或: || 逻辑与: && 逗号: , 复制代码 依次举例: const a = x => x ? f() : g(); // f() 和 g() 都在尾部。 复制代码 const a = () => f() || g(); // g()有可能是尾调用,f()不是 // 因为上述写法和下面的写法等效: const a = () => { const fResult = f(); // not a tail call if (fResult) { return fResult; } else { return g(); // tail call } } // 只有当f()的结果为falsey的时候,g()才是尾调用 复制代码 const a = () => f() && g(); // g()有可能是尾调用,f()不是 // 因为上述写法和下面的写法等效: const

JavaScript函数尾调用与尾递归

安稳与你 提交于 2019-11-28 01:56:33
什么是函数尾调用和尾递归 函数尾调用与尾递归的应用 一、什么是函数的尾调用和尾递归 函数尾调用就是指函数的最后一步是调用另一个函数。 1 //函数尾调用示例一 2 function foo(x){ 3 return g(x); 4 } 5 //函数尾调用示例二 6 function fun(x){ 7 if(x > 0){ 8 return g(x); 9 } 10 return y(x); 11 } 调用最后一步和最后一行代码的区别,最后一步的代码并不一定会在最后一行,比如示例二。还有下面这一种不能叫做函数尾调用: 1 // 下面这种情况不叫做函数尾调用 2 function fu(x){ 3 var y = 10 * x; 4 g(y); 5 } 为什么这种情况不叫作函数的尾调用呢?原因很简单,因为函数执行的最后一步是return指令,这个指令有两个功能,第一个功能是结束当前函数执行,第二个功能是将指令后面的数据传递出去(函数返回值)。而这个return指令不管有没有声明都会执行,没有声明的情况下返回的数据是undefined,所以上面的代码实际上是以下结构: 1 function fu(x){ 2 var y = 10 * x; 3 g(y); 4 return undefined; 5 } return指令是先关闭函数,然后再返回数据。说到这里,就会引发一个问题出来

说说尾递归

与世无争的帅哥 提交于 2019-11-28 01:15:50
微博上看到有人在讨论尾递归,想起以前曾看过老赵写的一篇相关的博客,介绍的比较详细了,相信很多人都看过,我也在下面留了言,但挑了个刺,表示文章在关键点上一带而过了,老赵自然是懂的,但看的人如果不深入思考,未必真正的明白,下面我说说我的理解。 什么是尾递归 什么是尾递归呢?(tail recursion), 顾名思议,就是一种“不一样的”递归,说到它的不一样,就得先说说一般的递归。对于一般的递归,比如下面的求阶乘,教科书上会告诉我们,如果这个函数调用的深度太深,很容易会有爆栈的危险。 // 先不考虑溢出问题 int func(int n) { if (n <= 1) return 1; return (n * func(n-1)); } 原因很多人的都知道,让我们先回顾一下函数调用的大概过程: 1)调用开始前,调用方(或函数本身)会往栈上压相关的数据,参数,返回地址,局部变量等。 2)执行函数。 3)清理栈上相关的数据,返回。 因此,在函数 A 执行的时候,如果在第二步中,它又调用了另一个函数 B,B 又调用 C.... 栈就会不断地增长不断地装入数据,当这个调用链很深的时候,栈很容易就满 了,这就是一般递归函数所容易面临的大问题。 而尾递归在某些语言的实现上,能避免上述所说的问题,注意是某些语言上,尾递归本身并不能消除函数调用栈过长的问题,那什么是尾递归呢?在上面写的一般递归函数

JavaScript函数尾调用与尾递归

半世苍凉 提交于 2019-11-27 15:13:48
什么是函数尾调用和尾递归 函数尾调用与尾递归的应用 一、什么是函数的尾调用和尾递归 函数尾调用就是指函数的最后一步是调用另一个函数。 1 //函数尾调用示例一 2 function foo(x){ 3 return g(x); 4 } 5 //函数尾调用示例二 6 function fun(x){ 7 if(x > 0){ 8 return g(x); 9 } 10 return y(x); 11 } 调用最后一步和最后一行代码的区别,最后一步的代码并不一定会在最后一行,比如示例二。还有下面这一种不能叫做函数尾调用: 1 // 下面这种情况不叫做函数尾调用 2 function fu(x){ 3 var y = 10 * x; 4 g(y); 5 } 为什么这种情况不叫作函数的尾调用呢?原因很简单,因为函数执行的最后一步是return指令,这个指令有两个功能,第一个功能是结束当前函数执行,第二个功能是将指令后面的数据传递出去(函数返回值)。而这个return指令不管有没有声明都会执行,没有声明的情况下返回的数据是undefined,所以上面的代码实际上是以下结构: 1 function fu(x){ 2 var y = 10 * x; 3 g(y); 4 return undefined; 5 } return指令是先关闭函数,然后再返回数据。说到这里,就会引发一个问题出来

函数式编程常用核心概念

ぃ、小莉子 提交于 2019-11-27 10:19:45
•纯函数 •函数的柯里化 •函数组合 •Point Free •声明式与命令式代码 •核心概念 1.纯函数 什么是纯函数呢? 对于相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用,也不依赖外部环境的状态的函数,叫做纯函数。 举个栗子: 1 2 3 4 5 var xs = [1,2,3,4,5]; // Array.slice是纯函数,因为它没有副作用,对于固定的输入,输出总是固定的 xs.slice(0,3); xs.slice(0,3); xs.splice(0,3); // Array.splice会对原array造成影响,所以不纯 xs.splice(0,3);    2.函数柯里化 传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。 我们有这样一个函数checkage: 1 var min = 18; <br> var checkage = age => age > min;    这个函数并不纯,checkage 不仅取决于 age还有外部依赖的变量 min。 纯的 checkage 把关键数字 18 硬编码在函数内部,扩展性比较差,柯里化优雅的函数式解决。 1 2 3 var checkage = min => (age => age > min); var checkage18 = checkage(18); // 先将18作为参数

解决Python递归栈溢出的问题

梦想的初衷 提交于 2019-11-27 02:23:24
在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数。 举个例子,我们来计算阶乘 n! = 1 x 2 x 3 x ... x n ,用函数 fact(n) 表示,可以看出: fact(n) = n! = 1 x 2 x 3 x ... x (n-1) x n = (n-1)! x n = fact(n-1) x n 所以, fact(n) 可以表示为 n x fact(n-1) ,只有n=1时需要特殊处理。 于是, fact(n) 用递归的方式写出来就是: def fact(n): if n==1: return 1 return n * fact(n - 1) 上面就是一个递归函数。可以试试: >>> fact(1) 1 >>> fact(5) 120 >>> fact(100) 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000L 如果我们计算 fact(5) ,可以根据函数定义看到计算过程如下: ===> fact(5) ===> 5 * fact(4) ===> 5 * (4 *

递归、尾递归与迭代

橙三吉。 提交于 2019-11-27 02:04:39
很久以前写过一篇《递归与迭代》,写得不是很好。可能是我自己也没把思路理清楚,现在就有了个重新整理思路炒冷饭的机会,也算是一个新的开始吧。 首先解释一个术语叫“尾调用”。直接从wiki的“ 尾调用 ”条目抄:尾调用是指一个函数里的最后一个动作是一个函数调用的情形:即这个调用的返回值直接被当前函数返回的情形。这种情形下称该调用位置为尾位置。若这个函数在尾位置调用本身(或是一个尾调用本身的其他函数等等),则称这种情况为尾递归,是递归的一种特殊情形。 比如,下面这个是尾调用: return f(); 而这个不是: return 1 + f(); 关于尾调用,有个重要的优化叫做尾调用消除。经过尾调用消除优化后的程序在执行尾调用时不会导致调用栈的增长。其实,这个优化可能不算是优化。因为在某些计算模型中,尾调用天然不会导致调用栈的增长。在EOPL提到过一个原理: 导致调用栈增长的,是对参数的计算,而不是函数调用。 由于尾调用不会导致调用栈的增长,所以尾调用事实上不像函数调用,而更接近GOTO。尾递归则等同于迭代,只是写法上的不同罢了。 下面以计算幂为例,分别用递归、尾递归和迭代三种方法实现程序。其中尾递归方法是递归方法的改进,而迭代方法纯粹是从尾递归方法翻译而来。 问题 已知数(整数,或浮点数,这里假设是整数)\(b\) 和非负整数\(n\) ,求\(b\)的\(n\)次幂\(b^n\)。