尾递归的那点事

尾递归的那点事

学习就是不断解惑的过程:
先找个问题:前几天偶然有人问到一个关于栈溢出的问题,说递归时为什么scala不容易比python出现栈溢出的问题?大概是这个意思,原话记不住了,当时也没多想,只是联想到递归是从上到下的计算流程,所以估计可能是记忆递归不必保存那么多栈帧或者是从下到上的计算流程,今天突然想到这个问题,记录一下。

回忆只需要看这里

  • 一句话结论:scala编译器会将<尾递归>优化为<累加器+迭代器>的循环计算
  • @tailrec:用来验证这个方法是否会使用尾递归优化进行编译,如果不能会报Recursive call not in tail position

笔记

问题有了,需要将问题拆分,然后一个个寻找答案

  1. 栈溢出是什么意思?
  2. 什么是递归?
  3. 什么是尾递归?
  4. 递归的优缺点分别是什么?
  5. 递归如何优化?

1.栈溢出是什么意思

栈溢出指的就是程序中定义的引用、指针等超过了栈结构预先设定的内存大小,最常见的就是递归调用,常见的报错如下:

Exception in thread "main" java.lang.StackOverflowError

2.什么是递归

递归: 直接或间接的调用自己,通过参数迭代,减小原问题的规模/量级,进而实现[大问题->小问题]的转换思想

[Recursion in Wiki]In computer science, recursion is a method of solving a problem where the solution depends on solutions to smaller instances of the same problem

3.什么是尾递归

尾递归: 顾名思义,递归调用放在尾部的递归方式,如:

def bee(num: Int, acc: Long): Long = {
  if (num <= 1) acc
  else bee(num - 1, acc + num)
}

4.递归的优缺点

优点

  1. 书写优雅、简单
  2. 逻辑符合动态规划的思路,清晰明了

缺点

  1. 需要保留大量的栈帧,经常出现栈溢出

5.如何优化

背景案例:求解1+2+3+4+…+n的和

def foo(num: Int): Long = {
	if (num <= 1) num.toLong
	else num + foo(num - 1)
}

递归的思想可以理解为:[从上到下的思维]要求解1->n的和,只需要将1->n-1的和求出来,再加上n即可<称为相关关系>,当n=1时,和可以很轻松计算为1<称为base case>;然后就开始按照保存的调用关系<即1-4的步骤>,计算最终结果<即5->8的步骤>

1->
2	->	
3		->
4			->
5			<-
6		<-	
7	<-
8<-

很明显,上面保存的调用关系[1-4]是可以优化掉的,只需要我们每次将上次的计算结果保存并传递给后续计算[累加器],然后通过计数等方式界定计算次数[迭代器/计数器]

def bar(num: Int): Long = {
	var acc = 0L;
	for (i <- 1 to num) { acc += i }
	acc
}

为了保持递归的优雅,将上述写法修改成尾递归的形式,尾递归中不得出现递归调用赋值、计算等操作。

@tailrec
def bee(num: Int, acc: Long): Long = {
  if (num <= 1) acc
  else bee(num - 1, acc + num)
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值