递归和返回关键字

我目前正在学习Java教程,现在正在使用Recursions。

我有以下代码来计算传递给阶乘方法的任何数字的阶乘

public class App { public static void main(String[] args) { //Eg 4! = 4*3*2*1(factorial 4) System.out.println(factorial(4)); } private static int factorial(int value){ //System.out.println(value); if (value == 1){ return 1; } return factorial(value - 1)*value; } } 

我无法理解这部分内容

 if (value == 1){ return 1; } return factorial(value - 1)*value; 

我的理解是return关键字只是终止方法,和/或返回方法声明的相同类型的值(即int,String等)。

运行以下行时会发生什么?

 return factorial(value - 1)*value; 

该函数返回总值(value – 1)*值,这将给我

 (3)*4 = 12 (2)*3 = 6 (1)*2 = 2 

每次传递迭代。但是, System.out.println(factorial(4)); 总共给了我24 。 这个数字是如何从这个方法得出的? 没有变量来存储值的总和,那么存储它们的程序在哪里? 另外,如何从这些值中获得24

 (3)*4 (2)*3 (1)*2 

虽然我知道24是从4*3*2*1得出的,但我不知道如何从上面计算出来。

任何解释将不胜感激。

你误解了

 return factorial(value - 1)*value; 

乘以的值是factorial(value - 1)value 。 换句话说,你可以像这样重写它:

 return (factorial(value - 1)) * value; 

所以当你通过4时,你会得到这个:

 factorial(3) * 4; 

这相当于

 (factorial(2) * 3) * 4; 

这相当于

 ((factorial(1) * 2) * 3) * 4; 

这相当于

 1 * 2 * 3 * 4; 

如果您使用调试器单步执行代码,您可以轻松地看到它的工作方式如下:

  1. 第一次调用函数会传递4 。 该函数计算if,然后调用自身,传递3 。 (第一个函数调用的状态保留在堆栈中,所以当这个调用返回时,我们可以从我们停止的地方继续,现在我们有了函数调用的结果。这个“堆栈”抽象实际上不需要理解递归。)

  2. 第二个函数调用计算if并调用自身,传递2

  3. 第三个函数调用计算if并调用自身,传递1
  4. 第四个函数调用计算if并返回1。
  5. 然后恢复第三个函数调用,将刚刚返回的函数( 1 )的返回值与其参数( 2 )的值相乘,返回结果( 2 )。
  6. 然后第二个函数调用重新开始,将刚刚返回的函数( 2 )的返回值与其参数( 3 )的值相乘,返回结果( 6 )。
  7. 然后第一个函数调用重新开始,将刚返回的函数( 6 )的返回值与其参数( 4 )的值相乘,返回结果( 24 )。

一些优化编译器会将递归调用更改为循环,但通常不可能将递归调用更改为固定表达式,例如1 * 2 * 3 * 4 ,因为在编译时您通常不知道递归的深度是多少。

如果您按如下所示修改代码然后使用调试器逐步执行此操作,则所有这些都将非常清楚:

 private static int factorial(int value){ if (value == 1){ return 1; } int recursiveResult = factorial(value - 1); return recursiveResult * value; } 

请注意,对于每个递归调用,我们必须在堆栈上存储“挂起”方法的状态,等待调用的结果。 因此,如果方法以递归方式调用自身(或者一系列方法在相互递归中调用自身),则堆栈有可能变满。 这称为堆栈溢出。 它通常是由导致递归循环的函数中的错误逻辑引起的:

 int stackOverflow() { return stackOverflow(); } 

它也可能由一个不逻辑循环的函数引起,但由于传递给它的数据而调用自身太多次。 例如,递归函数在处理树数据结构时很有用; 如果树太高,它可能会导致堆栈溢出。 以下是一些论点,但不会与其他人争论:

 void possibleStackOverflow(int arg) { if (arg == 0) return; possibleStackOverflow(arg - 1); } 

如果你调用possibleStackOverflow(10)你可能没问题,但possibleStackOverflow(-1)会抛出exception。

此外,通过VM实现限制,调用possibleStackOverflow(Integer.MAX_VALUE)将抛出StackOverflowException

您的return子句返回factorial(value-1)* value的结果,每个factorial(value-1)将被方法调用的结果替换。

这意味着阶乘(4)是:

(阶乘(1)*(阶乘(2 -1)*(阶乘(3 -1)*阶乘(4 – 1))))* 4

这将会

(1 *(2 * 3))* 4即24

 return factorial(value - 1)*value; 

返回value - 1阶乘value - 1倍。

它是这样的:

阶乘(4)

=阶乘(3)* 4

=阶乘(2)* 3 * 4

=阶乘(1)* 2 * 3 * 4

= 1 * 2 * 3 * 4 = 24

要了解递归,请尝试绘制调用树和参数:

 factorial(4) = 4 * factorial(4-1) | 3 * factorial(3-1) | 2 * factorial(2-1) | 1 

尝试使用递归的斐波纳契公式。

Java使用堆栈在递归调用之间存储信息。 有关详细信息,请参阅https://en.wikipedia.org/wiki/Call_stack 。

确实,“返回”这个词确实打破了当前的方法并返回一个值/结果。 但是,在’return’退出之前,需要在返回’statement’中有一个’expression’。

例如,返回1 + 1; 需要在返回之前评估操作’+’。 当你调用return func(arguments)时; java必须在返回之前调用该函数。 在这种情况下,它递归地通过这个“调用堆栈”(函数调用放在’堆栈’,其中最后一个是第一个被评估的)。

所以,要真正描述这里发生的事情:

1)return语句识别表达式

2)表达式调用堆栈上的函数

3)堆栈上的函数使用另一个函数进行另一次返回以进行评估

4)……

5)到达“基本情况”,其中找到“1”。

6)在堆栈中执行函数调用以评估表达式

您的递归方法只能以两种方式结束。 value 1且返回1,或者使用较小的value-1调用自身,并返回结果为的当前value和当前value

该方法可以编写得更加扩展,如下所示:

 private static int factorial(int value){ if (value == 1){ return 1; } int a = factorial(value - 1); return a * value; } 

从堆栈的角度来看,它看起来像这样。 我插入了虚构变量abc来表示递归调用返回时的值。

 factorial(4) |-- a = factorial(4-1=3) | |-- b = factorial(3-1=2) | | |-- c = factorial(2-1=1) | | | +-- return 1 // <-- deepest point in the stack | | +-- return c * 2 = 2 | +-- return b * 3 = 6 +-- return a * 4 = 24 

如果你输入一个更大的起始数字,那么堆栈会变得更深,重复调用factorial(value-1)直到它最终达到1然后堆栈展开以显示答案。 如果你输入更大的数字,最终会出现堆栈溢出 (这个站点的同名!),因为你的内存不足以容纳所需的所有变量。

我想,回来f(); 表示执行f()并返回f()的结果。在递归中,返回结果; 只是一个程序语句在递归函数f()之后运行。 所以它的执行顺序与调制递归函数相反。