📜  Java中的递归

📅  最后修改于: 2022-05-13 01:54:52.576000             🧑  作者: Mango

Java中的递归

什么是递归?
函数直接或间接调用自身的过程称为递归,相应的函数称为递归函数。使用递归算法,可以很容易地解决某些问题。此类问题的示例是河内塔 (TOH)、中序/前序/后序树遍历、图的 DFS 等。

递归的基本条件是什么?
在递归程序中,提供了基本情况的解决方案,并以较小的问题来表示较大问题的解决方案。

int fact(int n)
{
    if (n < = 1) // base case
        return 1;
    else    
        return n*fact(n-1);    
}

在上面的示例中,定义了 n < = 1 的基本情况,并且可以通过转换为较小的值来解决较大的数字值,直到达到基本情况。

如何使用递归解决特定问题?
这个想法是用一个或多个较小的问题来表示一个问题,并添加一个或多个停止递归的基本条件。例如,如果我们知道 (n-1) 的阶乘,我们计算阶乘 n。阶乘的基本情况是 n = 0。当 n = 0 时我们返回 1。

为什么递归时会出现 Stack Overflow 错误?
如果未达到或未定义基本情况,则可能会出现堆栈溢出问题。让我们举个例子来理解这一点。

int fact(int n)
{
    // wrong base case (it may cause
    // stack overflow).
    if (n == 100) 
        return 1;

    else
        return n*fact(n-1);
}

如果调用 fact(10),它将调用 fact(9)、fact(8)、fact(7) 等,但数量永远不会达到 100。因此,基本情况未达到。如果堆栈上的这些函数耗尽了内存,则会导致堆栈溢出错误。

直接递归和间接递归有什么区别?
如果函数fun 调用相同的函数fun,则称为直接递归函数。如果函数fun 调用另一个函数fun_new 和 fun_new 直接或间接调用 fun,则称为间接递归。表 1 说明了直接递归和间接递归之间的区别。

  • 直接递归:
    void directRecFun()
    {
        // Some code....
    
        directRecFun();
    
        // Some code...
    }
    
  • 间接递归:
    void indirectRecFun1()
    {
        // Some code...
    
        indirectRecFun2();
    
        // Some code...
    }
    
    void indirectRecFun2()
    {
        // Some code...
    
        indirectRecFun1();
    
        // Some code...
    }
    

有尾递归和无尾递归有什么区别?
当递归调用是函数执行的最后一件事时,递归函数是尾递归的。有关详细信息,请参阅尾递归文章。

如何在递归中将内存分配给不同的函数调用?
当从 main() 调用任何函数时,内存会在堆栈上分配给它。递归函数调用自身,被调用函数的内存分配在分配给调用函数的内存之上,并且为每个函数调用创建不同的局部变量副本。当达到基本情况时,函数将其值返回给调用它的函数,并释放内存并继续处理。

让我们通过一个简单的函数来举例说明递归是如何工作的。

// A Java program to demonstrate
// working of recursion
  
class GFG {
    static void printFun(int test)
    {
        if (test < 1)
            return;
  
        else {
            System.out.printf("%d ", test);
  
            // Statement 2
            printFun(test - 1);
  
            System.out.printf("%d ", test);
            return;
        }
    }
  
    public static void main(String[] args)
    {
        int test = 3;
        printFun(test);
    }
}
输出:
3 2 1 1 2 3

当从 main() 调用printFun(3)时,会为printFun(3)分配内存,并将局部变量 test 初始化为 3,并将语句 1 到 4 压入堆栈,如下图所示。它首先打印“3”。在语句 2 中,调用 printFun(2)并将内存分配给printFun(2)并将局部变量 test 初始化为 2 并将语句 1 到 4 压入堆栈。类似地, printFun(2)调用printFun(1)printFun(1)调用printFun(0)printFun(0)转到 if 语句并返回printFun(1) 。执行printFun(1)的其余语句,然后返回到printFun(2)等等。在输出中,打印从 3 到 1 的值,然后打印 1 到 3。内存堆栈如下图所示。

递归

递归编程相对于迭代编程的缺点是什么?
请注意,递归程序和迭代程序都具有相同的解决问题的能力,即每个递归程序都可以迭代地编写,反之亦然。递归程序比迭代程序具有更大的空间需求,因为所有函数都将保留在堆栈中,直到达到基本情况。由于函数调用和返回开销,它还具有更大的时间要求。

递归编程相对于迭代编程的优势是什么?
递归提供了一种简洁明了的代码编写方式。有些问题本质上是递归的,如树遍历、河内塔等。对于此类问题,最好编写递归代码。我们也可以在堆栈数据结构的帮助下迭代地编写这样的代码。例如参考无递归的中序树遍历,河内迭代塔。