尾递归(Tail Recursion)和Continuation
递归: 就是函数调用自己。 func() { foo(); func(); bar(); }
尾调用:就是在函数的最后,调用函数(包括自己)。 foo(){ return bar(); }
尾递归:就是在函数的最后,调用自身。 func() { foo(); return func(); }
尾递归是递归的优化,优化的目的是栈深度=1,永不StackOverflow。所有的递归都能转成尾递归。简单的场景,比如计算阶乘N!和Fibonacci数列,可以用parameter代替临时变量,实现尾递归。复杂的场景,比如对二叉树进行先序遍历(pre-order traversal, 深度优先遍历),就需要使用Continuation Passing Style(CPS)才能实现尾递归。
简单的场景比较好理解,定义里有多少个递归的临时变量,就用多少个参数即可。比如:
//阶乘:N! = (N-1)! * N
static int Factorial_TailRecursion(int target, int total = 1) {
if (target <= 1) {
return total;
} else {
return Factorial_TailRecursion(target - 1, total * target);
}
}
//Fibonacci数列:f(n) = f(n-1) + f(n-2)
static int Fibonacci_TailRecursion(int target, int n1 = 0, int n2 = 1) {
if (target <= 0) {
return n1;
} else {
return Fibonacci_TailRecursion(target - 1, n2, n1 + n2);
}
}
至于CPS的写法,比如func(int i, Func<int, int> continuation),要这么看:用func处理i返回的结果,还需要继续用continuation处理,这就是继续(Continuation)的含义。仍然以这个例子为例:
static int Factorial_Continuation(int target, Func<int, int> func) {
if (target <= 1) {
return func(1);
} else {
//以5为例,计算Fac(4)的值,后续*5再传入func
return Factorial_Continuation(target - 1, n => func(n * target));
}
}
static int Fibonacci_Continuation(int target, Func<int, int> func) {
if (target < 2) {
return func(target);
} else {
//以5为例,计算Fib(4)的值、后续计算Fib(3)的值、两值相+再传入func
return Fibonacci_Continuation(target - 1,
r1 => Fibonacci_Continuation(target - 2,
r2 => func(r1 + r2)));
}
}
参考
尾递归(Tail Recursion)和Continuation的更多相关文章
- 拾遗:关于“尾递归”- tail recursion
定义[个人理解]: 尾递归,即是将外层得出的常量计算因子,以函数参数的形式逐层向内传递,即内层调用整合外层调用的产出,整个递归的结果最终由最内层的一次函数调用得出:而通常的递归则是外层调用阻塞.等待内 ...
- Scala Tail Recursion (尾递归)
Scala对尾递归进行了优化,甚至提供了专门的标注告诉编译器需要进行尾递归优化.不过这种优化仅限于严格的尾递归,间接递归等情况,不会被优化. 尾递归的概念 递归,大家都不陌生,一个函数直接或间接的调用 ...
- 用尾递归和普通递归实现n!算法,二者比较
尾递归 - Tail Recursion尾递归是针对传统的递归算法而言的, 传统的递归算法在很多时候被视为洪水猛兽. 它的名声狼籍, 好像永远和低效联系在一起.尾递归就是从最后开始计算, 每递归一次就 ...
- Scala尾递归
递归函数应用 首先,我们来对比两个递归方法的求值步骤. 假设有方法gcd,用来计算两个数的最大公约数.下面是欧几里得算法的实现: def gcp(a: Int, b: Int): Int = if ( ...
- haskell中的cps
cps全称叫continuation passing style,简要来讲就是告诉函数下一步做什么的递归方式,由于普通递归有栈溢出的问题,而cps都是尾递归(tail recursion),尾递归则是 ...
- 泛函编程(3)-认识Scala和泛函编程
接着昨天的文章,再示范一个稍微复杂一点的尾递归tail recursion例子:计算第n个Fibonacci数.Fibonacci数第一.第二个数值分别是0,1,按顺序后面的数值是前面两个数的加合.例 ...
- Scala 常用语法
Clojure首先是FP, 但是由于基于JVM, 所以不得已需要做出一些妥协, 包含一些OO的编程方式 Scala首先是OO, Java语法过于冗余, 一种比较平庸的语言, Scala首先做的是简化, ...
- scheme Continuation
Continuation Pass Style在函数式编程(FP)中有一种被称为Continuation Passing Style(CPS)的风格.在这种风格的背后所蕴含的思想就是将处理中可变的一部 ...
- 递归、尾递归和使用Stream延迟计算优化尾递归
我们在学数据结构的时候必然会接触栈(Stack),而栈有一个重要的应用是在程序设计语言中实现递归.递归用途十分广泛,比如我们常见的阶乘,如下代码: 1234 public static int (in ...
随机推荐
- MFC通过ODBC方式连接mysql
一. 要安装mysql-connector-odbc驱动 二. 配置数据源 电脑的控制面板-管理工具-数据源ODBC-系统DSN-添加-选择刚刚安装的mysql odbc Driver 参考 http ...
- Hadoop编程1:天气数据AWK & MapReduce
本文介绍通过AWK和MapReduce两种方式统计出每年温度到最高气温直.awk速度虽然快,而且简短,但是数据量巨大到时候,就遇到力瓶颈,及时分布式执行awk脚本,也会出现机器死掉等问题,需要容错机制 ...
- [已解决] java.net.InetAddress.getHostName() 阻塞问题
在学习java nio的过程中发现某些情况下使用该方法会导致程序阻塞,(情况:服务器,Linux:客户端,WIN10) java.net.InetAddress.getHostName() 阻塞情况如 ...
- caffe问题集锦
不断更新中... 1.问题:check failure stack trace:*** 解决方法:路径错误,重新去看自己的路径是否有错误 2.Check failed: error == cudaSu ...
- Web_add_header
如果你发现所有的HTTP send请求都缺少头数据包,在脚本中的开头添加web_add_auto_header(”XXXXX“,”yyyy“);随着web_add_auto_header的添加,你不需 ...
- SDUTOJ 3312
题目描述 给出一个n*n的矩阵,矩阵中只有0和1,现在有两种操作: 1 x y 将第x行第y列的数字改变(0变1,1变0) 2 x1 y1 x2 y2求由左上角(x1,y1)到右下角(x2,y2)组成 ...
- 关于.net编译时目标生成平台
x86: 将程序集编译为由兼容 x86 的 32 位公共语言运行库运行. x64: 将程序集编译为由支持 AMD64 或 EM64T 指令集的计算机上的 64 位公共语言运行库运行. anycpu:( ...
- easyui datagrid 行右键生成 动态获取(toolbar) 按钮
var createGridRowContextMenu = function(e, rowIndex, rowData) { e.preventDefault(); var grid = $(thi ...
- async和await浅析
要理解async和await的用法,首先要了解Task相关知识,这里不做说明,因为这不是本文的重点. 如果你已经对Task很了解,那么如何使用async和await,在此主要总结了以下三点: 只有在a ...
- php中的匿名函数和闭包(closure)
一:匿名函数 (在php5.3.0 或以上才能使用) php中的匿名函数(Anonymous functions), 也叫闭包函数(closures), 允许指定一个没有名称的函数.最常用的就是回调函 ...