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

一、递归与尾递归

递归:

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

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

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

尾递归:

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

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

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

二、栈溢出

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

java.lang.StackOverflowError

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

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

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

三、尾递归优化

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

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

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

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

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

      return total + 1;
   }

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

©️2020 CSDN 皮肤主题: 撸撸猫 设计师: 设计师小姐姐 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值