我目前正在研究Java教程,目前正在进行递归。
我有以下代码来计算传递给阶乘方法的任何数字的阶乘
public class App {
public static void main(String[] args) {
//E.g 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
,但我不知道如何从上面计算出来。
非常感谢任何解释。
答案 0 :(得分:7)
你误解了
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;
如果您使用调试器单步执行代码,您可以轻松看到它的工作方式如下:
对函数的第一次调用传递4
。该函数评估if,然后调用自身,传递3
。 (第一个函数调用的状态保留在堆栈上,所以当这个调用返回时,我们可以继续我们离开的地方,现在我们有了函数调用的结果。这个“堆栈”抽象实际上不需要理解递归。)
第二个函数调用评估if并调用自身,传递2
。
1
。1
)的返回值乘以其参数(2
)的值,返回结果(2
)。2
)的返回值乘以其参数(3
)的值,返回结果(6
)。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)
会引发异常。
此外,通过VM实现限制,调用possibleStackOverflow(Integer.MAX_VALUE)将抛出StackOverflowException
答案 1 :(得分:1)
你的return子句返回factorial(value-1)*值的结果,每个factorial(value-1)将被方法调用的结果替换。
这意味着阶乘(4)是:
(阶乘(1)*(阶乘(2 -1)*(阶乘(3 -1)*阶乘(4 - 1))))* 4
这将是
(1 *(2 * 3))* 4即24
答案 2 :(得分:1)
return factorial(value - 1)*value;
返回value - 1
次value
的阶乘。
它是这样的:
阶乘(4)
= factorial(3)* 4
=阶乘(2)* 3 * 4
=阶乘(1)* 2 * 3 * 4
= 1 * 2 * 3 * 4 = 24
答案 3 :(得分:1)
要了解递归,请尝试绘制调用树和参数:
factorial(4) = 4 * factorial(4-1)
|
3 * factorial(3-1)
|
2 * factorial(2-1)
|
1
尝试使用递归的斐波纳契公式。
答案 4 :(得分:0)
Java使用堆栈在递归调用之间存储信息。有关详细信息,请参阅https://en.wikipedia.org/wiki/Call_stack。
答案 5 :(得分:0)
“return”这个词确实打破了当前方法并返回一个值/结果。但是,在返回'语句'中有一个'表达式',需要在'return'退出之前进行评估。
例如,返回1 + 1;需要在返回之前评估操作'+'。 当你调用return func(arguments)时; java必须在返回之前调用该函数。在这种情况下,它递归地通过这个“调用堆栈”(函数调用放在'堆栈',其中最后一个是第一个被评估的)。
所以,要真正描述这里发生了什么:
1)return语句识别表达式
2)表达式调用堆栈
上的函数3)堆栈上的函数使用另一个函数进行另一次返回以评估
4)...
5)达到“基本情况”,其中找到“1”。
6)在堆栈中执行函数调用以评估表达式
答案 6 :(得分:0)
您的递归方法只能以两种方式结束。 value
为1且返回1,或者使用较小的value-1
调用自身,并返回结果为当前value
的产品。
这个方法可以写得更加扩展如下:
private static int factorial(int value){
if (value == 1){
return 1;
}
int a = factorial(value - 1);
return a * value;
}
从堆栈的角度来看,它看起来像这样。我插入了虚构变量a
,b
和c
来表示递归调用返回时的值。
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
,然后堆栈展开以显示答案。如果你把多 多更大的数字,你最终会得到一个堆栈溢出(这个网站的名字!),因为你&#39; d内存不足以容纳所需的所有变量。
答案 7 :(得分:0)
按照我的想法,返回f();表示执行f()并返回f()的结果。在递归中,返回结果;只是一个程序语句在递归函数f()之后运行。因此,它的执行顺序与调制递归函数相反。