Java 递归与迭代(斐波那契序列)
我有两种不同的方法,一种是用迭代法计算第n个元素的斐波那契序列,另一种是用递归法做同样的事情Java 递归与迭代(斐波那契序列),java,recursion,iteration,Java,Recursion,Iteration,我有两种不同的方法,一种是用迭代法计算第n个元素的斐波那契序列,另一种是用递归法做同样的事情 程序示例如下所示: import java.util.Scanner; public class recursionVsIteration { public static void main(String[] args) { Scanner sc = new Scanner(System.in); //nth element input
程序示例如下所示:
import java.util.Scanner;
public class recursionVsIteration {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
//nth element input
System.out.print("Enter the last element of Fibonacci sequence: ");
int n = sc.nextInt();
//Print out iteration method
System.out.println("Fibonacci iteration:");
long start = System.currentTimeMillis();
System.out.printf("Fibonacci sequence(element at index %d) = %d \n", n, fibIteration(n));
System.out.printf("Time: %d ms\n", System.currentTimeMillis() - start);
//Print out recursive method
System.out.println("Fibonacci recursion:");
start = System.currentTimeMillis();
System.out.printf("Fibonacci sequence(element at index %d) = %d \n", n, fibRecursion(n));
System.out.printf("Time: %d ms\n", System.currentTimeMillis() - start);
}
//Iteration method
static int fibIteration(int n) {
int x = 0, y = 1, z = 1;
for (int i = 0; i < n; i++) {
x = y;
y = z;
z = x + y;
}
return x;
}
//Recursive method
static int fibRecursion(int n) {
if ((n == 1) || (n == 0)) {
return n;
}
return fibRecursion(n - 1) + fibRecursion(n - 2);
}
}
示例2(n=20)
示例#3(n=30)
我真正想知道的是为什么迭代突然变得更快,递归变得更慢。我很抱歉,如果我错过了这个问题的一些明显的答案,但我还是一个新的编程,我真的不明白背后发生了什么,我想知道。请提供一个很好的解释或者给我指出正确的方向,这样我就能自己找到答案。此外,如果这不是测试哪种方法更快的好方法,请让我知道并建议我不同的方法 提前感谢对递归和迭代进行了比较,并介绍了它们在生成斐波那契数方面的应用 如文中所述 性能差的原因是在每个递归调用的病态级别中寄存器的大量推送pop 这基本上说明递归方法有更多的开销
另外,请看一下在执行斐波那契算法的递归实现时,您通过反复重新计算相同的值来添加冗余调用
fib(5) = fib(4) + fib(3)
fib(4) = fib(3) + fib(2)
fib(3) = fib(2) + fib(1)
请注意,fib(2)
将对fib(4)
和fib(3)
进行冗余计算。
然而,这可以通过一种称为的技术来克服,该技术通过存储已经计算过一次的值来提高递归斐波那契的效率。对于已知值的fib(x)
的进一步调用可以被简单的查找所取代,从而消除了进一步递归调用的需要
这是迭代法和递归法之间的主要区别,如果您感兴趣,还有其他更多的计算斐波那契数的方法。为了简洁起见,让F(x)为递归斐波那契数
F(10) = F(9) + F(8)
F(10) = F(8) + F(7) + F(7) + F(6)
F(10) = F(7) + F(6) + F(6) + F(5) + 4 more calls.
....
所以你要打F(8)两次,
F(7)3次,F(6)5次,F(5)7次。。等等
因此,输入越大,树就越大。使用递归的方法,时间复杂度是
O(fib(n))
,这是非常昂贵的。迭代方法是O(n)
这没有显示,因为a)您的测试非常短,代码甚至不会被编译b)您使用了非常小的数字
这两个示例运行得越快。一旦循环或方法被调用10000次,就应该将其编译为本机代码 如果有人对数组的迭代函数感兴趣:
public static void fibonacci(int y)
{
int[] a = new int[y+1];
a[0] = 0;
a[1] = 1;
System.out.println("Step 0: 0");
System.out.println("Step 1: 1");
for(int i=2; i<=y; i++){
a[i] = a[i-1] + a[i-2];
System.out.println("Step "+i+": "+a[i]);
}
System.out.println("Array size --> "+a.length);
}
publicstaticvoidfibonacci(inty)
{
int[]a=新的int[y+1];
a[0]=0;
a[1]=1;
System.out.println(“步骤0:0”);
System.out.println(“步骤1:1”);
对于(inti=2;i为什么递归速度较慢?
当您再次调用函数本身(作为递归)时,编译器将分配新的激活记录(只需将其视为普通堆栈)对于该新函数。该堆栈用于保存状态、变量和地址。编译器为每个函数创建一个堆栈,此创建过程将继续,直到达到基本情况。因此,当数据大小变大时,编译器需要较大的堆栈段来计算整个过程。计算和管理在这个过程中,这些记录也会被计算在内
此外,在递归中,堆栈段在运行时被提升。编译器不知道编译时将占用多少内存
这就是为什么如果你不正确处理你的基本情况,你会得到StackOverflow异常:)。我更喜欢使用黄金数的数学解。享受吧
private static final double GOLDEN_NUMBER = 1.618d;
public long fibonacci(int n) {
double sqrt = Math.sqrt(5);
double result = Math.pow(GOLDEN_NUMBER, n);
result = result - Math.pow(1d - GOLDEN_NUMBER, n);
result = Math.round(result / sqrt);
return Double.valueOf(result).longValue();
}
每当你在寻找完成一个特定算法所需的时间时,最好总是考虑时间复杂度
用O(某物)来评估纸上的时间复杂度
比较上述两种方法,迭代方法的时间复杂度为O(n),而递归方法的时间复杂度为O(2^n)
让我们试着找出fib(4)
迭代方法,循环计算4次,因此其时间复杂度为O(n)
递归方法
fib(4)
fib(3) + fib(2)
fib(2) + fib(1) fib(1) + fib(0)
fib(1) + fib(0)
因此fib()被调用了9次,当n的值很大时,它比2^n稍低,甚至很小(请记住,BigOh
(O)负责上限
)
因此,我们可以说迭代方法是在多项式时间内进行计算的,而递归方法是在指数时间内进行计算的。您使用的递归方法效率不高。我建议您使用尾部递归。与此相反,尾部递归只在在任何时间点进行e堆栈
public static int tailFib(int n) {
if (n <= 1) {
return n;
}
return tailFib(0, 1, n);
}
private static int tailFib(int a, int b, int count) {
if(count <= 0) {
return a;
}
return tailFib(b, a+b, count-1);
}
public static void main(String[] args) throws Exception{
for (int i = 0; i <10; i++){
System.out.println(tailFib(i));
}
}
public static int tailFib(int n){
如果(n我有一个递归解决方案,您可以将计算值存储在何处,以避免进一步不必要的计算。下面提供了代码
public static int fibonacci(int n) {
if(n <= 0) return 0;
if(n == 1) return 1;
int[] arr = new int[n+1];
// this is faster than using Array
// List<Integer> lis = new ArrayList<>(Collections.nCopies(n+1, 0));
arr[0] = 0;
arr[1] = 1;
return fiboHelper(n, arr);
}
public static int fiboHelper(int n, int[] arr){
if(n <= 0) {
return arr[0];
}
else if(n == 1) {
return arr[1];
}
else {
if( arr[n-1] != 0 && (arr[n-2] != 0 || (arr[n-2] == 0 && n-2 == 0))){
return arr[n] = arr[n-1] + arr[n-2];
}
else if (arr[n-1] == 0 && arr[n-2] != 0 ){
return arr[n] = fiboHelper(n-1, arr) + arr[n-2];
}
else {
return arr[n] = fiboHelper(n-2, arr) + fiboHelper(n-1, arr );
}
}
}
公共静态int fibonacci(int n){
if(n)递归调用函数会增加开销。这两种方法不完全相同(甚至不包括图中的递归/迭代)第一个问题:您的基准测试方法存在严重缺陷。您确实没有做足够的工作来准确测量差异。您应该使用System.nanoTime
,并重复调用多次,以便测量有用的工作量。接下来,看看每个调用的复杂性……计算出eac中完成了多少工作提示:试着在纸上走一遍,如果你把fibRecursion(8)和FibreIteration(8)叫做什么。最快的方法是
fib(4)
fib(3) + fib(2)
fib(2) + fib(1) fib(1) + fib(0)
fib(1) + fib(0)
public static int tailFib(int n) {
if (n <= 1) {
return n;
}
return tailFib(0, 1, n);
}
private static int tailFib(int a, int b, int count) {
if(count <= 0) {
return a;
}
return tailFib(b, a+b, count-1);
}
public static void main(String[] args) throws Exception{
for (int i = 0; i <10; i++){
System.out.println(tailFib(i));
}
}
public static int fibonacci(int n) {
if(n <= 0) return 0;
if(n == 1) return 1;
int[] arr = new int[n+1];
// this is faster than using Array
// List<Integer> lis = new ArrayList<>(Collections.nCopies(n+1, 0));
arr[0] = 0;
arr[1] = 1;
return fiboHelper(n, arr);
}
public static int fiboHelper(int n, int[] arr){
if(n <= 0) {
return arr[0];
}
else if(n == 1) {
return arr[1];
}
else {
if( arr[n-1] != 0 && (arr[n-2] != 0 || (arr[n-2] == 0 && n-2 == 0))){
return arr[n] = arr[n-1] + arr[n-2];
}
else if (arr[n-1] == 0 && arr[n-2] != 0 ){
return arr[n] = fiboHelper(n-1, arr) + arr[n-2];
}
else {
return arr[n] = fiboHelper(n-2, arr) + fiboHelper(n-1, arr );
}
}
}