📜  Kotlin递归(递归函数)和尾递归

📅  最后修改于: 2020-10-05 14:58:02             🧑  作者: Mango

在本文中,您将学习创建递归函数。一个自我调用的函数 。此外,您还将了解尾递归函数。

调用自身的函数称为递归函数。并且,这种技术称为递归。

一个物理世界的例子是放置两个相互面对的平行反射镜。它们之间的任何对象都将递归地反映出来。


递归在编程中如何工作?

fun main(args: Array) {
    ... .. ...
    recurse()
    ... .. ...
}

fun recurse() {
    ... .. ...
    recurse()
    ... .. ...
}

在这里, recurse() 函数是从的机构,称为recurse() 函数本身。该程序的工作原理如下:

Kotlin中的递归函数调用

在这里,递归调用将永远持续下去,从而导致无限递归。

为了避免无限递归,可以在一个分支进行递归调用而其他分支不递归调用的情况下使用if … else(或类似方法)。


示例:使用递归查找数字的阶乘

fun main(args: Array) {
    val number = 4
    val result: Long

    result = factorial(number)
    println("Factorial of $number = $result")
}

fun factorial(n: Int): Long {
    return if (n == 1) n.toLong() else n*factorial(n-1)
}

运行该程序时,输出为:

Factorial of 4 = 24

该程序如何工作?

下图解释了递归调用factorial() 函数 :

递归在Kotlin中如何工作?

涉及的步骤如下:

factorial(4)              // 1st function call. Argument: 4
4*factorial(3)            // 2nd function call. Argument: 3
4*(3*factorial(2))        // 3rd function call. Argument: 2
4*(3*(2*factorial(1)))    // 4th function call. Argument: 1 
4*(3*(2*1))                 
24

Kotlin尾递归

尾递归是一个通用概念,而不是Kotlin语言的功能。包括Kotlin在内的某些编程语言使用它来优化递归调用,而其他语言(例如Python )则不支持它们。


什么是尾递归?

在普通递归中,您首先执行所有递归调用,然后最后从返回值计算结果(如上例所示)。因此,在进行所有递归调用之前,您不会得到结果。

在尾部递归中,首先执行计算,然后执行递归调用(递归调用将当前步骤的结果传递到下一个递归调用)。这使得递归调用等效于循环,并避免了堆栈溢出的风险。


尾递归的条件

如果对自身的函数调用是它执行的最后一个操作,则该递归函数可以进行尾部递归。例如,

示例1:不符合尾部递归的条件,因为对自身n*factorial(n-1)的函数调用不是最后一个操作。

fun factorial(n: Int): Long {

    if (n == 1) {
        return n.toLong()
    } else {
        return n*factorial(n - 1)     
    }
}

示例2:有资格进行尾递归,因为对自身的函数调用fibonacci(n-1, a+b, a)是最后一个操作。

fun fibonacci(n: Int, a: Long, b: Long): Long {
    return if (n == 0) b else fibonacci(n-1, a+b, a)
}

要告诉编译器在Kotlin中执行尾部递归,您需要使用tailrec修饰符标记该函数 。


示例:尾递归

import java.math.BigInteger

fun main(args: Array) {
    val n = 100
    val first = BigInteger("0")
    val second = BigInteger("1")

    println(fibonacci(n, first, second))
}

tailrec fun fibonacci(n: Int, a: BigInteger, b: BigInteger): BigInteger {
    return if (n == 0) a else fibonacci(n-1, b, a+b)
}

运行该程序时,输出为:

354224848179261915075

该程序计算斐波那契数列的 100 项。由于输出可以是非常大的整数,因此我们从Java标准库中导入了BigInteger类。

在这里, 函数 fibonacci()被标记为tailrec修饰符,并且该函数可以进行尾递归调用。因此,在这种情况下,编译器会优化递归。


如果尝试在不使用尾部递归的情况下找到Fibonacci系列的 20000 项(或任何其他大整数),则编译器将抛出java.lang.StackOverflowError异常。但是,我们上面的程序可以正常工作。这是因为我们使用了尾部递归,它使用了基于循环的高效版本,而不是传统的递归。


示例:使用尾递归的阶乘

上述示例(第一个示例)中用于计算数字阶乘的示例无法针对尾递归进行优化。这是执行相同任务的另一个程序。

fun main(args: Array) {
    val number = 5
    println("Factorial of $number = ${factorial(number)}")
}

tailrec fun factorial(n: Int, run: Int = 1): Long {
    return if (n == 1) run.toLong() else factorial(n-1, run*n)
}

运行该程序时,输出为:

Factorial of 5 = 120

编译器可以在此程序中优化递归,因为递归函数可以进行尾递归,并且我们使用了tailrec修饰符,该修饰符告诉编译器优化递归。