为什么递归与尾递归会栈溢出?优化原理是什么?

一、递归与尾递归

递归:

在函数的定义中使用函数自身的方法

Kotlin代码实现一个n的累加的函数

fun recursive(n:Int):Int {
    if (n == 1) {
        return 1
    } else {
        return n + recursive(n - 1)
    }
}

尾递归:

若函数在尾位置调用自身(或是一个尾调用本身的其他函数等等),则称这种情况为尾递归。尾递归也是递归的一种特殊情形。尾递归是一种特殊的尾调用,即在尾部直接调用自身的递归函数。对尾递归的优化也是关注尾调用的主要原因。尾调用不一定是递归调用,但是尾递归特别有用,也比较容易实现。

Kotlin代码实现一个n的累加的函数

fun tailRecursive(index:Int, total:Int):Int { 
    if (index == 1) {
        return 1  
    } else {
        return tailRecursive(index - 1, total+index) //在尾部调用自身
    }
}

二、栈溢出

在java中出现栈溢出,会报错

java.lang.StackOverflowError

下面来看一下,函数是如何调用的,在函数A里调用函数B,我们称A为调用者函数,B是被调用函数。每一次函数的调用,都会有如下的信息进栈,占用内存(每个进程中只有一部分虚拟内存是用来调用函数的),内存不够用就会出现这种栈溢出的错误。
在这里插入图片描述

在函数调用中,函数返回,就会进行出栈操作。如果递归次数很大,不断的进栈,不出栈,就会出现栈溢出。递归和尾递归,都存在这样的问题。

更多函数调用的细节,参考这里

三、尾递归优化

尾递归优化原理在编译阶段,把递归转换成了迭代(循环),从而避免了栈溢出

Kotlin中在尾递归函数前面加上关键字 tailrec

tailrec fun tailRecursive(n:Int, total:Int):Int {
    if (n == 1) {
        return 1
    } else {
        return tailRecursive(n - 1, total+n)
    }
}

加上关键字 tailrec后,Kotlin编译器会在进行了尾递归优化,下面的代码是Kotlin生成的汇编,decompile后的java代码

   public static final int tailRecursive(int n, int total) {
      while(n!= 1) {
         int var10000 = n- 1;
         total += n;
         n= var10000;
      }
      return 1;
   }

参考:
尾递归为啥能优化?
函数调用栈 剖析+图解
StackOverflowError的分析和理解

发布了242 篇原创文章 · 获赞 775 · 访问量 224万+

猜你喜欢

转载自blog.csdn.net/xx326664162/article/details/85331528