递归在Java中实际上是如何工作的?

递归在Java中实际上是如何工作的?,java,recursion,Java,Recursion,我刚刚接触过这种递归,我知道至少它是一种 一个方法在执行期间调用它自己,但我对它实际上是如何调用的感到非常困惑 工作 在学校的书中,实现阶乘函数是第一个例子 这是给的 //int n = 5; private static int factorial(int n) { if(n == 0) { return 1; } else return n * factorial(n - 1); } 我知道这个阶乘是如何工作的,但我不能完全确定

我刚刚接触过这种递归,我知道至少它是一种 一个方法在执行期间调用它自己,但我对它实际上是如何调用的感到非常困惑 工作

在学校的书中,实现阶乘函数是第一个例子 这是给的

//int n = 5;
private static int factorial(int n) {
    if(n == 0) {
        return 1;
    }   
    else
        return n * factorial(n - 1);
}
我知道这个阶乘是如何工作的,但我不能完全确定我是否理解正确。对于方法引用自身的每次调用,
n
乘以阶乘参数
n-1
,直到它达到
1

这就是我在脑海中追踪这种递归的方式

5 * factorial(5 - 1) = 20 //first call
20 * factorial(4 - 1) = 60 //second call
60 * factorial(3 - 1) = 120 //third call
120 * factorial(2 - 1) = still 120 // fourth call

120 is the last returned value;
给出的另一个例子是使用递归而不是普通循环打印从1到n的数字

//int n = 10;
private static void printNumbers(int n) {
    if(n >= 1) {
        printNumbers(n - 1);
        System.out.print(n + " ");
    }
}
这将输出:
12345678910

现在,让我困惑的是为什么输出从
1
开始?因为从我的理解来看,输出应该从
9
开始,一直到
1

因为从我的理解来看,在开始时,
n=10
,然后
10>1
,所以它当然会再次调用自己,然后
10-1
,然后打印
9
,然后再次调用自己,
9-1
,然后打印
8
,等等

任何人都可以简单地向我澄清这一点,并纠正我对我提到的例子的错误理解,我在这里有点困惑,我在StackOverflow上看到的关于递归的大部分帖子都没有真正的帮助(但是如果你知道这里有一个关于递归的非常好和清晰的答案,请给我链接)


谢谢…

因为事实上,直到最后一次调用
factorial()
返回
1
时,才会发生任何事情。在内部,所有中间结果都“堆积”在堆栈上,然后在函数返回时“堆积”

所以实际上,调用
factorial(3)
就可以了

factorial(3) = 3 * factorial(2) -->> 3 on the heap
factorial(2) = 2 * factorial(1) -->> 2,3 on the heap
factorial(1) = 1 * factorial(0) -->> 1,2,3 on the heap
factorial(0) = 1
一旦
阶乘(0)
最终返回,所有乘法都将汇总:

1 * 1 * 2 * 3 = 6
或者用另一种方式写:

factorial(0) = 1
factorial(1) = 1 * (1)
factorial(2) = 2 * (1 * (1))
factorial(3) = 3 * (2 * (1 * (1)))
factorial(4) = 4 * (3 * (2 * (1 * (1))))
...

每个左括号都是一个函数调用,它在堆栈上推送一个值,只有当左值和右值都已知时,乘法才会发生,也就是当它达到
0
并返回
1
时,递归决不是一个简单的概念——大多数人都有问题,所以你并不孤单

递归地思考一个问题意味着重复一些计算,直到问题得到解决(即达到基本情况或用尽方法。对于阶乘,当n=0时,对于数字打印,用尽方法[不做任何事情])。发生这种情况时,最新的计算将返回到递归级别,直到没有其他级别为止。这样,问题就解决了

这里有一个类比:把这个过程想象成你在采矿。挖掘的计算(或过程)是(粗略地)挖掘,直到你找到你想要的东西(比如说宝石)。你从地球表面开始。有宝石吗?这个问题是你的基本情况,当你发现你的宝石时,你将停止挖掘。如果没有宝石,你必须挖得更深(下一层),所以你需要调用挖掘程序。你又问自己,有宝石吗?如果没有宝石,你必须挖得更深,等等。这将重复,或重复,直到你可以回答“是的!我发现了宝石。“一旦基本情况达到,你就处于过程的最低水平,对吗?在采矿时,一旦你找到了宝石,你就不能呆在矿的底部。你必须带着你所有的宝石回到顶端

简而言之,这就是递归。现在谈谈你的理解:

您的条件是正确的,10>=1,所以调用printNumbers(10-1),以此类推。但是,它使用1打印,因为这是第一个返回的内容。如果查看代码,您会注意到printNumber(10-1)在System.out.println(n+“”)之前被调用;。这意味着在打印10之前,PrintNumber运行9。当我们谈到printNumber(1)时,会发生以下情况:

  • 一大于或等于一吗?是的,让我们继续
  • 打印号码(1-1)
  • 零是否大于或等于一?不,因为在条件之后没有任何内容,所以该方法将耗尽并返回
  • System.out.println(1+“”)
  • 现在打印数字(1)已完成,下一步是什么
  • System.out.println(2+“”)
  • 现在打印数字(2)已经完成,接下来是什么
  • 依此类推,直到printNumbers(10)-第一次调用printNumbers
  • System.out.println(10+“”)
  • 没别的了,问题解决了

真的就这些了。如果有任何困惑,请告诉我

我假设这个问题更多的是关于计算的顺序,而不是关于理解递归的概念

使用递归,事情的发生方式与您列出的相反。它的工作方式类似于堆栈,而不是队列。如果你玩过像MTG这样的纸牌游戏,它就像在游戏中一样有效。“激活”的最后一件事是先完成的

基本上,第一次调用阶乘函数时,它会在这一点上暂停:

return n * factorial(n - 1);
因为您的程序现在正在等待阶乘(n-1)的结果继续

它将继续调用子例程(如果子例程足够大,可能会导致堆栈溢出异常,即此站点的名称)

最终,n将等于0,该分支将执行:

return 1;
因此,在堆栈的顶部,您将有阶乘(1)等待阶乘(0)的结果

return 1 * factorial(0); //come on hurry up factorial(0)!
现在处理完0后,1就可以返回它的值了

return 1 * 1; //1 replaces factorial(0), now we can return!
现在我们在N=2,仍然停留在同一个地方

return 2 * factorial(1); //need factorial(1) to continue!
阶乘(1)返回后,我们得到:

return 2 * 1; //ready to return!
然后在N=3时(或阶乘(3)

…等等,直到

return n * factorial(n - 1); //been waiting forever...
另一个例子是:

//int n = 10;
private static void printNumbers(int n) {
    if(n >= 1) {
        printNumbers(n - 1);
        System.out.print(n + " ");
    }
}
程序将在打印编号(n-1)处停止
//int n = 10;
private static void printNumbers(int n) {
    if(n >= 1) {
        printNumbers(n - 1);
        System.out.print(n + " ");
    }
}
public int getRSquare(int n){
    if(n==0){
        return 1;
    }
    return getRSquare(n-1)*n;
} 
result = getSquare( n=3);        
               getSquare(n=2)     
                      getSquare(n =1)     
                             getSquare(0)
                              Return a 1,
                         return returned value multiplied by n  (n is a 1 prev returned value is 1 = 1)
                  return returned value multiplied by n  (n is a 2 prev returned value is 1 = 2)
          return returned value multiplied by n  (n is a 3 prev returned value is 2 = 6)
public int getRSquare(int n){
    if(n==0){
        return 1;
    }
    return n * getRSquare(n-1);
}