Kotlin 遞歸和尾遞歸

在本文中,您將學(xué)習(xí)創(chuàng)建遞歸函數(shù)。一個自我調(diào)用的函數(shù)。此外,您還將了解尾遞歸函數(shù)。

調(diào)用自身的函數(shù)稱為遞歸函數(shù)。并且,這種技術(shù)稱為遞歸。

一個物理世界的實例是將兩個平行的鏡子相對放置。它們之間的任何對象都將被遞歸地反射。

遞歸在編程中如何工作?

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

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

在這里,從recurse()函數(shù)本身的主體中調(diào)用recurse()函數(shù)。 該程序的工作原理如下:

Kotlin中的遞歸函數(shù)調(diào)用

在這里,遞歸調(diào)用永遠持續(xù)下去,從而導(dǎo)致無限遞歸。

為了避免無限遞歸,可以在一個分支進行遞歸調(diào)用而其他分支不遞歸調(diào)用的情況下使用if ... else(或類似方法)。

示例:使用遞歸查找數(shù)字的階乘

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()下圖說明了該函數(shù)的遞歸調(diào)用:

遞歸在Kotlin中如何工作?

涉及的步驟如下:

factorial(4)              // 第1次函數(shù)調(diào)用,參數(shù): 4
4*factorial(3)            // 第2次函數(shù)調(diào)用,參數(shù): 3
4*(3*factorial(2))        // 第3次函數(shù)調(diào)用,參數(shù): 2
4*(3*(2*factorial(1)))    // 第4次函數(shù)調(diào)用,參數(shù): 1 
4*(3*(2*1))                 
24

Kotlin 尾遞歸

尾遞歸不是Kotlin語言的特征,而是一個通用概念。 包括 Kotlin 在內(nèi)的一些編程語言使用它來優(yōu)化遞歸調(diào)用,而其他語言(例如 Python )不支持它們。

什么是尾遞歸?

在普通遞歸中,首先執(zhí)行所有遞歸調(diào)用,最后根據(jù)返回值計算結(jié)果(如上面的示例所示)。所以,在進行所有遞歸調(diào)用之前,您不會得到結(jié)果。

在尾遞歸中,首先執(zhí)行計算,然后執(zhí)行遞歸調(diào)用(遞歸調(diào)用將當(dāng)前步驟的結(jié)果傳遞給下一個遞歸調(diào)用)。 這使得遞歸調(diào)用等同于循環(huán),并避免了堆棧溢出的風(fēng)險。

尾遞歸的條件

如果對自身的函數(shù)調(diào)用是它執(zhí)行的最后一個操作,則該遞歸函數(shù)可以進行尾部遞歸。例如,

示例1:不符合尾部遞歸的條件,因為對其自身的函數(shù)調(diào)用 n*factorial(n-1) 不是最后一個操作。

fun factorial(n: Int): Long {

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

示例2:符合尾遞歸條件,因為對自身的函數(shù)調(diào)用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中執(zhí)行尾部遞歸,您需要使用tailrec修飾符標記該函數(shù)。

示例:尾遞歸

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

這個程序計算斐波納契級數(shù)的第100項。 因為輸出可以是非常大的整數(shù),所以我們從Java標準庫中導(dǎo)入了BigInteger類。

在這里,函數(shù)fibonacci()用trarec修飾符標記,該函數(shù)有資格進行尾遞歸調(diào)用。 因此,編譯器在這種情況下優(yōu)化了遞歸。

如果您試圖在不使用尾遞歸的情況下查找斐波納契數(shù)列的第20000項(或任何其他大整數(shù)),編譯器將拋出 java.lang.StackOverflowError 異常。
但是,我們上面的程序運行得很好。 這是因為我們使用了尾遞歸,它使用了高效的基于循環(huán)的版本,而不是傳統(tǒng)的遞歸。

示例:使用尾遞歸的階乘

上述示例(第一個示例)中用于計算數(shù)字階乘的示例無法針對尾遞歸進行優(yōu)化。 這是執(zhí)行相同任務(wù)的另一個程序。

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

編譯器可以在該程序中優(yōu)化遞歸,因為遞歸函數(shù)可以進行尾遞歸,并且我們使用了tailrec修飾符,告訴編譯器優(yōu)化遞歸。

丰满人妻一级特黄a大片,午夜无码免费福利一级,欧美亚洲精品在线,国产婷婷成人久久Av免费高清