我们使用值n
进行递归。我们将此值保存在变量x
中。然后,变量x
被作为返回值添加到递归中。究竟如何运作?
我尝试了不同的方法来检查您是否首先对x
使用递归,然后再使用x
将其添加到另一个递归中。
#include <stdio.h>
#include <stdlib.h>
int up(int n)
{
int x;
if(n == 0)
{
return 0;
}
else if(n == 1)
{
return 1;
}
else
{
x = up(n - 2);
return x + up(n - 1);
}
}
int main(void)
{
int n = 20;
int res;
res = up(n);
printf("Result %d\n", res);
return 0;
}
结果是6765。由于这是学校提供的代码,因此结果应该不错。我只是不明白为什么。
答案 0 :(得分:0)
听起来您在递归地把握函数作用域的概念时遇到了问题。通常,将递归视为树/堆栈最容易。
堆栈:
对于一个简单的例子,假设n = 5。
Up(5)被推入堆栈。
然后,您声明此函数范围内的x是UP(3)。
现在将UP(3)压入堆栈,并且UP(5)等待Up(3)返回。
堆叠顺序:
Up(3)
Up(5)
现在,Up(3)在此函数内部声明x为UP(1),并耐心等待Up(1)返回。由于x是在函数内部声明的,因此它们是局部变量,只能在声明它们的函数内部访问。这意味着Up(3)内部的X与Up(5)内部的X存储在内存中完全不同的位置。当您研究地址空间以及如何存储局部变量等时,您肯定会对此有所了解。但是继续前进!
堆栈:
Up(1)
Up(3)
Up(5)
Up(1)返回1,因此我们将其弹出堆栈!这意味着Up(3)可以继续运行。
堆叠:
Up(3)
Up(5)
Up(3)现在具有x = 1(Up(1)的返回,并希望返回x + Up(2)。为此,我们将Up(2)推入堆栈。
堆栈:
Up(2)
Up(3)
Up(5)
继续此示例,Up(2)将Up(0)添加到堆栈中。 Up(0)将返回0,因此Up(2)范围内的x设置为0。然后Up(2)将Up(1)添加到堆栈中,并返回1。Up(2)将返回(0 + 1)。现在我们可以通过从堆栈弹出Up(2)回到Up(3)。 Up(3)的x = 1,并希望返回(1 + 1)。从堆栈弹出Up(3),然后在堆栈上留下Up(5)。由于返回了Up(3),X现在设置为2。现在我们将Up(4)添加到堆栈中,该堆栈按此顺序添加Up(2),Up(0),Up(1),Up(3),Up(2),Up(0),Up(1)注意(在添加新呼叫之前,其中许多呼叫会弹出。)
我建议阅读有关全局范围和功能范围的内容。理解这些概念对于理解递归的基础至关重要。但是,您已经可以看到这种类型的递归是蛮力的,根本没有优化。例如,对于创建的每个递归调用,您要多次计算Up(2)。使用较大的数字时,这会导致可伸缩性问题,因为您要对同一数字重复多次相同的操作。可以通过一个记忆数组和一个较小的Big O运行时更多的逻辑来解决此问题,但是对于这个问题来说,可能在细节上太过复杂了。
答案 1 :(得分:0)
参数绑定对于每个调用都是唯一的。这意味着n
中的up(20)
不会与n
中的up(19)
混合。由于它不是参考,因此n - 1
会作为新值进行计算并作为新值传递到旧的位置,因此在返回旧的n
时,它就是附加调用期间的状态。局部变量也仅存在于作用域中,因此每个x
也不同。
由于此函数是纯函数,因此它的结果是算法和参数的唯一乘积,我认为您应该从基本情况开始……
基本案例:
up(0) => 0
up(1) => 1
默认情况:
up(2) => up(2-2) + up(2-1) => up(0) + up(1) => 0 + 1
现在我从那里的基本情况中知道了0
和1
的结果,您可以将其替换为结果。我们可以走得更远:
up(3) => up(3-2) + up(3-1) => 1 + 1 => 2
up(4) => up(4-2) + up(4-1) => 1 + 2 => 3
up(5) => up(5-2) + up(5-1) => 2 + 3 => 5
...
这个(斐波那契)序列的有趣之处在于,如果您拥有前两个数字,则可以生成下一个。这使得该序列成为尾递归或迭代循环的极佳候选者,两者均远优于易于阅读的递归版本。
答案 2 :(得分:0)
以下是该功能可以简化多少:
int Fib(int n)
{
return (n<2 ? n : Fib(n-1) + Fib(n-2));
}
这是简单的C,而不是C ++