English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Kotlin 재귀와 테일 재귀

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

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

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

递归在编程中如何工作?

fun main(args: Array<String>) {
    ... .. ...
    recurse()
    ... .. ...
}
fun recurse() {
    ... .. ...
    recurse()
    ... .. ...
}

在这里,从recurse()函数本身的主体中调用recurse()函数。 该程序的工作原理如下:

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

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

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

fun main(args: Array<String>) {
    val number = 4
    val result: Long
    result = factorial(number)
    println("$number 阶乘 = $result")
}
fun factorial(n: Int): Long {
    return if (n == 1) n.toLong() else n*factorial(n-1)
}

이 프로그램을 실행할 때, 출력은 다음과 같습니다:

4 阶乘 = 24

该程序如何工作?

factorial()下图说明了该函数的递归调用:

涉及的步骤如下:

factorial(4)              // 제1다음 함수 호출, 매개변수: 4
4*factorial(3)            // 제2다음 함수 호출, 매개변수: 3
4*(3*factorial(2))        // 제3다음 함수 호출, 매개변수: 2
4*(3*(2*factorial(1))    // 제4다음 함수 호출, 매개변수: 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 접 heads모자이크로 함수를 표시해야 합니다.

예제: 尾递归

import java.math.BigInteger
fun main(args: Array<String>) {
    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

이 프로그램은斐波나치 수열의 nth항을 계산합니다100항. 출력은 매우 큰 정수일 수 있으므로, 우리는 Java 표준 라이브러리에서 BigInteger 클래스를 가져왔습니다.

여기서 함수 fibonacci()는 trarec 접 heads모자이크로 표시되었으며, 이 함수는 尾递归 호출에资格이 있습니다. 따라서 컴파일러는 이 경우 재귀를 최적화했습니다.

斐波나치 수열의 nth항을 찾으려고 시도할 때 尾递归를 사용하지 않는 경우20000항(또는 어떤 다른 큰 정수)에서 컴파일러는 java.lang.StackOverflowError 예외를 발생시킵니다.
그러나 우리의 프로그램은 잘 실행됩니다. 이는 우리가 전통적인 재귀 대신 효율적인 루프 기반 버전을 사용한 尾递归를 사용했기 때문입니다.

예제: 尾递归를 사용한 조단계수

위의 예제(첫 번째 예제)에서 숫자의 조단계수를 계산하는 예제는 尾递归를 최적화할 수 없습니다. 이는 같은 작업을 수행하는 다른 프로그램입니다.

fun main(args: Array<String>) {
    val number = 5
    println("$number 팩토리얼 = ${factorial(number)}")
}
tailrec fun factorial(n: Int, run: Int = 1): Long {
    return if (n == 1) run.toLong() else factorial(n-1, run*n)
}

이 프로그램을 실행할 때, 출력은 다음과 같습니다:

5 팩토리얼= 120

최적화 가능한 재귀 함수가 있기 때문에 컴파일러는 이 프로그램에서 재귀를 최적화할 수 있습니다. 재귀 함수는 테일 재귀가 가능하며, tailrec 접 heads가 컴파일러에게 재귀를 최적화하도록 알립니다.