文章目录
简述
什么是递归?
递归函数会反复调用自身,每次处理一个子问题,直至到达结束条件。
递归的突出缺点?
容易导致栈溢出:在递归调用过程中,系统为每一层递归开辟栈空间以存储返回点、局部变量等信息。递归次数过多可能导致栈溢出等问题,因此在设计程序时需要注意栈空间的管理。不停的创建新的栈,直到资源耗尽,出现爆栈。
在JAVA中运行时数据区中,有个专门的栈空间,即虚拟机栈区(VM Stack),栈溢出是线程请求的栈的深度超过虚拟机允许的最大值,会报异常StackOverflowError。
什么是尾递归?
针对递归的缺点进行的优化。使用衡量的内存,不增加栈,只更新栈。
递归函数是自己调用自己,尾递归是把递归调用写到最后,递归调用是函数的最后一条语句。
尾递归会对于编译器进行优化,因为编译器可以将尾递归优化为循环结构(这里就很类似迭代了),从而避免递归调用过深导致栈溢出的问题
效果可以见下文的代码示例
JVM不支持尾递归
-
Java编译器没有计划直接支持尾递归优化的
https://wiki.openjdk.org/display/loom/Main
Tail Calls
We envision explicit tail-call elimination. It is not the intention of this project to implement automatic tail-call optimization.
-
JVM尚未支持尾递归优化,更主要是历史遗留问题
https://www.zhihu.com/question/521221911
https://www.youtube.com/watch?v=2y5Pv4yN0b0\&t=3725s(借用,需要魔法)
Brian Goetz(Java语言首席架构师)曾经在一个演讲中解释过。JDK里面过去有一些诡异的安全机制,会去数当前调用栈究竟有多少个帧。如果帧数不对,就会报错。然而尾递归优化就是要减少栈帧
-
尾递归优化会更改调用栈,对于程序员debug来说非常不方便
scala有限支持尾递归
编译器优化:虽然Scala编译器可以优化尾递归,但不是所有的Scala编译器都保证这一点。例如,在Scala.js和一些特定的Scala版本中,尾递归优化可能不完全适用或需要特定设置。确保你的编译环境支持尾递归优化。
使用注解 @tailrec,来源import scala.annotation.tailrec,或者高版本的已经可以自动支持尾递归了
示例
scala
package com.donny
object test {
/**
* 尾递归
*
* @return
*/
def t(n: Int, sum: Long): Long = {
if (n == 1) {
return 1 + sum
}
t(n - 1, n + sum)
}
/**
* 普通递归
*/
def t1(n: Int): Long = {
if (n == 1) {
return 1
}
n + t1(n - 1)
}
def main(args: Array[String]): Unit = {
System.out.println(t1(19000))
System.out.println(t(1000000000, 0))
}
}
迭代
迭代算法是一种通过反复执行一组指令来解决问题的方法。与递归算法不同,迭代算法不涉及自我调用,而是通过循环结构反复执行一定的步骤,直至达到问题的解决或终止条件。
迭代算法的特点包括:
- 循环结构:使用循环结构反复执行一组指令,每次迭代都向问题解决迈进一步。
- 明确终止条件:迭代算法必须定义明确的终止条件,以确保循环不会无限进行。这一条件标志着算法已经完成任务或达到了所需的解。
- 无递归调用:与递归算法不同,迭代算法不涉及函数或方法的自我调用。算法通过重复执行相同的操作来逐步逼近问题的解。
- 效率:迭代算法通常具有较高的运行效率,尤其在处理大规模数据或需要重复执行相似操作的情况下。
迭代算法通过循环结构、明确的终止条件和无递归调用的方式提供了一种直观而高效的问题解决方法。
特点:
迭代法,又被称为辗转法,是一种通过反复使用变量的先前值来递推新值的过程。与迭代法相对应的是直接法,也称为一次解法,其特点是一次性解决问题。
在迭代法中,问题的解决通过多次迭代步骤逐渐逼近,每一步都利用先前的值计算出新的值。这反映了迭代法的渐进逼近性质,使得问题的解随着迭代的进行逐步精炼。
与直接法不同,迭代法通过多次迭代过程,通过递推更新变量的值,逐步逼近问题的最终解。这种方法常用于处理复杂问题,其中直接求解可能困难或不可行。
代码示例
递归的调用栈展示
java
public class Test {
static long t(int n, long sum) {
if (n == 0) {
int err = 1 / 0; // 故意的报错,展示堆栈
return sum;
}
return t(n - 1, n + sum);
}
public static void main(String[] args) {
System.out.println(t(5, 0));
}
}
结果
java
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.donny.Test.t(Test.java:11)
at com.donny.Test.t(Test.java:14)
at com.donny.Test.t(Test.java:14)
at com.donny.Test.t(Test.java:14)
at com.donny.Test.t(Test.java:14)
at com.donny.Test.t(Test.java:14)
at com.donny.Test.main(Test.java:19)
迭代的调用栈展示
java
public class Test {
static long t(int n, long sum) {
while (n > 0) {
sum = n + sum;
n = n - 1;
}
int res = 1 / 0;
return sum;
}
public static void main(String[] args) {
System.out.println(t(5, 0));
}
}
结果
java
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.donny.Test.t(Test.java:20)
at com.donny.Test.main(Test.java:25)
形式上的尾递归
java
public class Test {
static long t(int n, long sum) {
if (n == 0) {
return sum;
}
return t(n - 1, n + sum);
}
public static void main(String[] args) {
System.out.println(t(19000, 0));
}
}
递归的爆栈
java
Exception in thread "main" java.lang.StackOverflowError
at com.donny.Test.t(Test.java:13)
at com.donny.Test.t(Test.java:13)
at com.donny.Test.t(Test.java:13)
at com.donny.Test.t(Test.java:13)
at com.donny.Test.t(Test.java:13)
...
...
迭代的使用
java
public class Test {
static long t(int n, long sum) {
while (n > 0) {
sum = n + sum;
n = n - 1;
}
return sum;
}
public static void main(String[] args) {
System.out.println(t(19000, 0));
}
}
迭代的结果
java
180509500